What you’ll take away
- The RP ID scopes a credential; the full origin identifies the calling web context and includes its scheme and relevant port.
- A subdomain can satisfy the ordinary RP-ID rule while failing a service’s narrower expected-origin policy.
- The assertion binds client context cryptographically, but valid signatures still need expected origin, challenge and policy checks.
- Related Origin Requests are an explicit supported mechanism; publishing their allowlist does not automatically change server acceptance.
- A credential remains associated with its registration RP ID; a later request using a parent name does not rename it.
Set up a service with one approved sign-in origin
Imagine a fictional service that signs people in at https://login.example.com. Its passkeys were registered for the relying party ID example.com. The service deliberately accepts authentication responses only from the origin https://login.example.com. These are two different settings: the relying party ID defines the credential's scope, while the accepted origin identifies where the web sign-in request is expected to begin. WebAuthn's specification separates those ideas, and Google's server guide requires checking the expected origin as part of authentication. [1][3] Passing the browser's domain-scope check is therefore not the same as being accepted by the service.
Now add a fictional shop at https://shop.example.com. That hostname sits under the same example.com domain. Under the ordinary domain rule, it can request example.com as the relying party ID, but our server still rejects an assertion whose origin is the shop because the shop is outside its chosen sign-in allowlist. [1][3] That is the puzzle this article explains. All addresses and outcomes are hypothetical, with top-level HTTPS pages, an available matching credential and no special cross-site authorization. A permitted request also still needs the user's participation and all remaining verification checks; the domain relationship does not sign anyone in automatically.
Define a hypothetical relying party with RP ID example.com and an expected-origin set containing only https://login.example.com. Assume the credential was registered under that RP ID. The RP ID and the expected origin are separate inputs to the authentication design: one scopes the credential, while the other constrains the browser context accepted by the server. The baseline domain relationship is documented in the WebAuthn Level 2 Recommendation; Google's authentication guide separately requires server-side origin verification. [1][3] Client-side permission to initiate a ceremony is not an authorization decision by the relying-party server.
For a top-level caller at https://shop.example.com, example.com satisfies the ordinary registrable-domain-suffix relationship. Nevertheless, an assertion carrying the shop origin fails this service's exact expected-origin policy. [1][3] All examples below assume normal HTTPS origins, no Related Origin Requests opt-in, no legacy AppID extension and no embedded cross-origin frame, unless a section explicitly changes an assumption. They also assume a matching credential is available and the required user interaction occurs. These conditions isolate the verification question without implying that every permitted caller can silently obtain an assertion or that a successful domain check completes authentication.
An origin is more specific than a familiar domain name
For the HTTPS addresses in our example, an origin consists of the scheme, host and port, with the normal default port omitted when it is written as a string. A page path is not part of it. [6] Thus https://login.example.com/account and https://login.example.com/help have the same origin. Adding the usual HTTPS port, 443, does not create a different origin. Using the non-default port 8443 does: https://login.example.com:8443 is a different origin from the one our fictional service accepts. A longer address is not necessarily a different origin; the parts that changed matter.
An RP ID, by contrast, is a domain string rather than a complete origin, so our example.com value contains neither https:// nor a port. [1] The browser's domain-scope decision cannot substitute for the server's more specific origin decision. This also explains why a familiar word somewhere in an address is insufficient. In https://example.com.signin.test, the actual hostname ends in signin.test; example.com is merely an earlier part of the name. Our examples are about parsed address components and domain boundaries, not how similar a logo, title or sign-in form looks to the expected site.
For an HTTPS URL, the serialized origin identifies its scheme and host, plus a non-default port when applicable. The path, query and fragment do not create a new origin. MDN's URL documentation also illustrates omission of the protocol's default port. [6] Consequently, the account and help paths under https://login.example.com share an origin, and explicitly writing :443 normalizes to that same origin. A URL using :8443 has a distinct origin. These comparisons concern ordinary HTTPS URLs; opaque, file and native-application origins are outside this worked example.
The RP ID omits scheme and port and uses a valid domain string. [1] It therefore cannot encode all the information represented by the origin. A scope check against example.com and an expected-origin check against https://login.example.com are deliberately different predicates. Hostname comparison must also respect labels: example.com.signin.test is under signin.test, not under example.com. The example illustrates why substring resemblance is not the specified domain-suffix relation. It supplies no implementation shortcut; production code should use the platform and verification library's defined origin and RP-ID handling rather than improvising string matching.
The browser checks whether the requested RP ID fits
Start with a page at https://login.example.com. Under WebAuthn's ordinary rule, it can use login.example.com as its RP ID or explicitly use the parent example.com. It cannot choose the public suffix com, a child such as m.login.example.com, or an unrelated domain such as example.net. [1] The rule follows domain boundaries, not a service's brand ownership. The same company owning two websites does not by itself make one website's passkey available to the other. A deliberate related-origin mechanism is a separate possibility, which we will return to later.
For https://shop.example.com, requesting example.com also fits the ordinary domain rule. That says the browser may continue this part of the request; it does not say our server approves the shop as a sign-in origin. The authenticator separately uses the requested RP ID to find a credential with matching scope. [1] The steps answer different questions: is this caller allowed to request that scope, is a matching credential available, and will the service accept the returned assertion? Keeping those questions separate makes a rejected shop-origin response understandable without assuming that the browser or the passkey has failed.
The baseline client rule permits an RP ID equal to the caller's effective domain or a registrable-domain suffix of it. The specification's example permits login.example.com and example.com for an origin using login.example.com, while rejecting com and m.login.example.com. [1] The first rejected value is a public suffix; the second is a more specific child, not the permitted parent relation. An unrelated registrable domain also fails this baseline relationship. The hypothetical cases use that ordinary rule and do not silently include newer related-origin authorization or native-application association rules.
The caller https://shop.example.com can therefore satisfy the baseline request-scope rule for example.com. Credential selection is a separate constraint: a credential registered for another RP ID does not become a matching credential simply because the caller's domain check passed. The specification describes the authenticator enforcing the credential's RP ID and the client locating eligible credentials with user participation. [1] A successful browser predicate should be recorded as exactly that. It supplies no evidence that the server's expected-origin, challenge, credential ownership, signature and user-verification requirements have all been met.
The server still checks the actual calling origin
Return to the shop example. Suppose a matching credential is available and an authentication response is produced. Its client data identifies the shop's full origin. The server expects https://login.example.com, so it rejects the response under the policy we set at the start. Client data includes the origin of the requesting context, and server verification checks it against the expected sign-in origin. [2][3] The two hostnames sharing a parent domain does not erase that difference. Allowing example.com as the credential scope is not the same policy as accepting every subdomain as a sign-in page.
Now change only the page path: a request from https://login.example.com/help still has the expected origin. [6] That passes this particular check, but it does not show that the page was intended to offer sign-in or that every remaining check passes. An origin policy draws a web security boundary; it is not a review of each page's user interface. For a service owner, the useful exercise is to write down the exact origins the server should accept and compare them with the actual deployment. A response should not become acceptable merely because its origin contains a familiar name.
For the invented service, compare the assertion's collected client origin against the fixed singleton allowlist {https://login.example.com}. A response with origin https://shop.example.com fails that predicate even when its credential scope is example.com. The browser supplies the full origin in clientDataJSON, and Google's verification procedure includes matching it to the site's expected sign-in origin. [2][3] The configured expected value must represent the relying party's policy, not be inferred from an untrusted response simply to make that response pass. The example's two checks intentionally operate at different levels of specificity.
An alternate path on the approved login origin does not change this origin predicate. [6] It remains necessary to distinguish origin acceptance from application intent and from the rest of assertion verification. The example does not claim that a server can recognize a trustworthy page layout from WebAuthn fields. It shows the narrower result: a valid scope relationship does not justify broadening the expected-origin policy to all siblings. For an implementation review, compare the intended accepted origins, the configured library inputs and the actual origin received, while preserving every other verification requirement.
The origin belongs to the signed context
The response is more than a message that says which page asked. WebAuthn client data includes the operation type, challenge and origin. The authenticator's signature covers its authenticator data together with a SHA-256 hash of that client data. [2][5] The server uses the registered public key and the required verification procedure to check the response. [3] This connection matters: the site is not supposed to trust an origin field merely because somebody sent it. The claimed context must be part of the cryptographically verified response, and its values must also match what the server expected.
Imagine taking a genuine response from the shop and replacing its origin text with the approved login origin. That changes the client data covered by the signature, so the old signature is no longer valid for the altered data under the verification procedure. [5] This is a conceptual integrity example, not a request to manipulate a real sign-in. It also shows the limit of signature verification alone: a genuine signature over an unexpected shop origin can still be rejected by policy. Cryptographic consistency and acceptable context are both necessary; one does not stand in for the other.
Collected client data includes the ceremony type, encoded challenge and full origin. The assertion signature binds authenticatorData and the SHA-256 hash of clientDataJSON. [2][5] The server verifies against the stored credential public key and the applicable algorithm, then enforces the expected contextual values as part of the verification procedure. [3] Parsing a JSON origin is therefore insufficient by itself, and a signature check without the expected-origin predicate is also insufficient. The authenticated representation and the server's policy have separate roles in the acceptance decision.
Changing the origin within clientDataJSON changes the signed input represented by its hash; an existing valid signature does not authenticate the altered message. [5] Conversely, successful signature verification establishes consistency with the relevant private-key operation, not that every signed value meets the relying party's requirements. A correctly signed shop-origin response remains outside our singleton origin policy. This distinction is the core of the worked example. It assumes a conforming browser, authenticator and verification process; it is not a claim that origin binding solves compromised-client behavior or validates every application action following a login.
Change one condition in the same fictional response
Consider a baseline response for our fictional login origin. Assume its credential belongs to the expected account, its RP-ID binding matches example.com, its challenge is the current one, its signature verifies, and the presence and verification flags meet the service's requirements. The illustrated checks pass. Now change just the origin to the shop in a separately validly signed response: the origin check rejects it. Keep the approved origin but supply an earlier challenge instead: the challenge check rejects it. These are hypothetical cases showing the consequences of the rules, not results from a running service or an attempt to bypass them. [1][3]
Two more changes identify other boundaries. If this service requires user verification and the corresponding flag is absent, the response fails that requirement even at the correct origin. If the signature is invalid, matching text fields cannot rescue it. [1][3] Likewise, an RP-ID hash for a different credential scope is not accepted as example.com. [1] The pattern is useful when reading a sign-in error: the correct domain does not prove every check succeeded. Each failure concerns a defined condition, and a single successful-looking field is not an overall authentication result.
Define a baseline assertion with a known account-to-credential association, expected RP ID, approved origin, current challenge, valid signature, user presence and required user verification. Assume the remaining applicable protocol checks also succeed. This constructed baseline is accepted under the stated policy. A second, independently validly signed assertion with the shop origin fails the origin predicate; one carrying an old challenge fails the freshness predicate. [1][3] Changing one logical condition at a time isolates the rejection reason. The cases are analytical examples, not generated assertions, test vectors or evidence that a particular implementation was exercised.
If user verification is required, a false UV flag fails that policy even if the origin is correct. An invalid signature fails regardless of matching visible fields. [1][3] The authenticator data's rpIdHash must also correspond to the expected RP ID; a different binding is not interchangeable. [1] These checks form a conjunction, not a majority vote. The illustration intentionally omits encoding details, extension handling and implementation-specific account/session integration. It is useful for reviewing the meaning of a failure, but should not be copied as a complete verifier or treated as permission to skip checks absent from the small example.
An approved origin does not make an old response fresh
Our fictional service creates a new challenge for each sign-in attempt and keeps the expected value until it verifies that attempt. Google's guide calls for a challenge that is hard to predict, not reused, and discarded after use or expiry. [3] The returned client data contains an encoded form of that challenge. [2] In a paper example, we might label two challenges A and B, but those letters are only labels for different values; they are not suitable real challenges. A response for A does not answer a new request for B, even when both came from the approved login origin.
The signature connects the response to its context, while the server's saved expectation tells it which attempt that context belongs to. [2][3][5] That is why a familiar origin and a mathematically valid old signature are not enough. The server must compare against the correct current expectation and reject reuse according to its verification procedure. For someone investigating a failed sign-in, this suggests a bounded question: was the response checked against the challenge for that attempt? It does not justify printing private signing material or replacing a failed check with a more permissive one.
Challenge validation binds the response to the intended authentication attempt. Google's server guide requires a cryptographically secure, unique challenge, persistence for subsequent verification, disposal after the attempt and expiry handling; it explicitly rejects reuse. [3] clientDataJSON carries the base64url representation of the challenge. [2] Let A and B denote distinct opaque challenge byte sequences, purely as notation. A signature over the context containing A does not satisfy a verifier expecting B. The notation is not a proposal for low-entropy challenge strings, a test vector or a specific session-storage implementation.
The expected challenge must be obtained from the relying party's state for the relevant attempt rather than adopted from the response being verified. This follows from the guide's stored-expectation comparison. [3] Signature integrity establishes that the response's context has not simply been rewritten; the expected value and single-use policy establish relevance and freshness for the attempt. Both are required for the stated behavior. Origin matching alone answers neither question. The article deliberately does not prescribe a timeout duration or provide storage code, because those would add implementation choices beyond this focused verification example.
Changing the requested RP ID does not rename a credential
There is another way a request can pass the browser's domain check and still lack the expected credential. Suppose a fictional passkey was originally registered for login.example.com, not example.com. A page on the login host is allowed to request either scope under the ordinary rule, but the existing credential remains associated with the RP ID used when it was created. The specification and Google's guide require that authentication use the credential's matching RP ID. [1][3] Writing the broader parent domain in a later request does not relabel the earlier credential.
Keep this case separate from allowing a second origin for credentials that were already registered under example.com. In one case, the intended credential scope changed; in the other, the scope stayed fixed while the accepted calling contexts changed. [1][4] Those different changes explain different failures. A useful configuration record therefore includes the registration RP ID as well as the current request RP ID and expected origin. The record should use ordinary identifiers and documented settings. It does not need secret keys, and it should not infer the registered scope merely from the website address visible today.
A credential's RP-ID association is established during registration. Consider a credential created under login.example.com and a later get() request specifying example.com. Although either identifier can satisfy the baseline client domain relationship from the login host, the credential's own RP ID must match the requested scope. [1][3] Passing the request-scope predicate does not transform an existing credential association. This is a distinct explanation for an unavailable or rejected credential, separate from the server rejecting an assertion's origin. The example concerns logical scope and does not prescribe a migration procedure.
Changing accepted origins while retaining an RP ID and changing the RP ID itself therefore have different consequences. Related-origin configuration operates around a common chosen RP ID, whereas an already registered credential retains its original scope. [1][4] A deployment review should distinguish registration options, authentication options, stored credential metadata and expected verifier inputs. Treating all four as a single domain setting hides the failure mechanism. Preserve the actual values and their provenance, and use the relevant library and service documentation to determine the supported transition instead of assuming that a broader-looking parent name automatically covers earlier credentials.
Keep a verification record with a clear boundary
For the fictional service, a useful review record answers five questions: which RP ID was registered and requested, which complete origins are accepted, which challenge belongs to this attempt, whether the signature verifies with the stored credential, and whether the required user checks were satisfied. Those are explanations of what the verifier must establish, not instructions to collect authentication secrets. The official server guide recommends using an appropriate FIDO server library for the verification work. [3] The small cases in this article are a way to understand its inputs and failures, not a replacement implementation.
Read the result at the right level. A browser allowing example.com as the RP ID tells you about the caller's permitted scope. A server accepting https://login.example.com tells you about its origin policy. A complete accepted assertion requires the other checks too. [1][3] This makes the original shop rejection unsurprising: the parent domain matched, but the service had deliberately chosen a narrower sign-in origin. Clear settings and explicit expected values turn a confusing passkey failure into a question that can be investigated without weakening the mechanism that produced it.
A focused acceptance record links credential scope, caller origin, configured expected origins, the current server-held challenge, signature verification and required flags. Credential-to-account ownership and all remaining applicable protocol checks still belong to the real verifier. The W3C procedure is substantially broader than the illustrated cases, and Google's guide delegates verification to a FIDO server library. [1][3] This article's purpose is to explain why individually plausible fields do not imply a valid whole assertion. It does not supply production verification code, certify an implementation or replace the library's version-specific integration requirements.
The useful conclusion is a precise separation of predicates. The client validates the request's permitted RP scope; the authenticator uses a matching credential; the server verifies the signed response against its expected context and policy. [1][2][3][5] Under the defined configuration, a shop-origin request may pass the first relationship and fail the later origin requirement. Keeping the full origin distinct from the RP ID makes that outcome coherent. It also makes configuration changes reviewable: each new origin or identifier changes a specific expectation, rather than silently expanding what a successful sign-in is assumed to prove.
Sources & further reading
Explore the references behind this guide. External pages may change after our review.
- Web Authentication: An API for accessing Public Key Credentials - Level 2W3C · Accessed 6 Nov 2026
- AuthenticatorResponse: clientDataJSON property - Web APIs | MDNMDN Web Docs · Accessed 6 Nov 2026
- Server-side passkey authentication | Web guides | Google for DevelopersGoogle for Developers · Accessed 6 Nov 2026
- Allow passkey reuse across your sites with Related Origin Requests | web.devGoogle web.dev · Accessed 6 Nov 2026
- AuthenticatorAssertionResponse: signature property - Web APIs | MDNMDN Web Docs · Accessed 6 Nov 2026
- URL: origin property - Web APIs | MDNMDN Web Docs · Accessed 6 Nov 2026
Article updated 6 Nov 2026.
Found something we should revisit?
Good explanations get better with thoughtful feedback.