WEB / WEB-15.S01
PKCE, redirects and identity binding
Separate code continuity, callback correlation and local account binding.
1. Core · Mechanism
Read every scenario without motion
Normal login
- Callback start
The scene starts after the ordinary callback reached the RP; pending tx-A and AS code-A bindings are declared fixtures.
- Atlas RP
- Callback held; no session
- AS / OP
- Code-A bound; no token
- Local callback gate
The RP accepts browser-A/state-A binding to pending tx-A. It prepares a token request to the trusted AS endpoint carrying code-A, the registered redirect and verifier reference; the AS has not yet received it.
- Atlas RP
- Callback accepted; token request ready
- AS / OP
- Code-A bound; no token
- Token request arrived
The ordinary declared TLS token request reached the AS; code and verifier aliases were received, with validation still pending.
- Atlas RP
- Callback accepted
- AS / OP
- Request received; pending
- AS code/verifier gate
The supplied verifier produces S256 matching the code-A challenge. Client/redirect bindings, unused=true and now=1050 < expiresAt=1100 also pass; the AS prepares the token response. This is not yet RP identity acceptance.
- Atlas RP
- Callback accepted
- AS / OP
- Token aliases ready
- AS response received
The AS response reached the RP; this arrival is not marked as identity acceptance or session creation.
- Atlas RP
- Response received; RP pending
- AS / OP
- Token aliases ready
- RP response gate
The RP reads issuer/audience/expiry/key and selected nonce gates for an ordinary token response; an error response establishes no identity.
- Atlas RP
- Identity accepted; mapping pending
- AS / OP
- Token aliases ready
- Local mapping policy
Login through an existing iss/sub mapping and linking a new identity to an existing account are separate local policy decisions.
- Atlas RP
- Mapping found
- AS / OP
- Token aliases ready
- Local result
The ordinary mapping creates a login session; pending confirmation or an earlier denial produces no link/store mutation.
- Atlas RP
- Login session created
- AS / OP
- Token aliases ready
State deny
- Callback start
The scene starts after the ordinary callback reached the RP; pending tx-A and AS code-A bindings are declared fixtures.
- Atlas RP
- Callback held; no session
- AS / OP
- Code-A bound; no token
- Local callback gate
The RP checked the selected issuer and pending browser/state binding; this step creates no HTTP transfer.
- Atlas RP
- State denied; no exchange
- AS / OP
- Code-A bound; no token
- Transaction rejected
Selected state-B did not match tx-A; AS state remains unchanged because the RP starts no ordinary token request.
- Atlas RP
- No exchange; no ID gate
- AS / OP
- Code-A bound; no token
- No session created
The callback ended in local denial; neither token receipt nor a local account-linking result is produced.
- Atlas RP
- No session; no link
- AS / OP
- Code-A bound; no token
PKCE deny
- Callback start
The scene starts after the ordinary callback reached the RP; pending tx-A and AS code-A bindings are declared fixtures.
- Atlas RP
- Callback held; no session
- AS / OP
- Code-A bound; no token
- Local callback gate
The RP accepts browser-A/state-A binding to pending tx-A. It prepares a token request to the trusted AS endpoint carrying code-A, the registered redirect and verifier reference; the AS has not yet received it.
- Atlas RP
- Callback accepted; token request ready
- AS / OP
- Code-A bound; no token
- Token request arrived
The ordinary declared TLS token request reached the AS; code and verifier aliases were received, with validation still pending.
- Atlas RP
- Callback accepted
- AS / OP
- Request received; pending
- AS code/verifier gate
S256 derived from the received verifier does not match the challenge associated with code-A. The AS prepares invalid_grant and issues no new token alias. The RP has not yet received this error response and does not know the result.
- Atlas RP
- Callback accepted
- AS / OP
- invalid_grant ready
- AS response received
The AS response reached the RP; this arrival is not marked as identity acceptance or session creation.
- Atlas RP
- Response received; RP pending
- AS / OP
- invalid_grant ready
- RP response gate
The RP handles the received invalid_grant response. It is not an ID-token response, so no positive issuer, audience, signature or nonce validation is established and no local identity is created.
- Atlas RP
- Error handled; no identity
- AS / OP
- invalid_grant ready
- Local mapping policy
Without a validated identity, the RP performs no accepted login mapping or new account link. The earlier AS denial does not become a local account mutation here.
- Atlas RP
- No mapping change
- AS / OP
- invalid_grant ready
- Local result
This branch stopped at the AS verifier comparison: no token was issued and the RP accepted no identity. No new login session or account link is created; invalidation of another existing session is not inferred.
- Atlas RP
- No session; no link
- AS / OP
- invalid_grant ready
Link pending
- Callback start
The callback has reached the RP with supplied tx-A/code-A bindings. The local account requested for linking is already authenticated. The new external identity and account link have not yet been accepted.
- Atlas RP
- Callback received; existing account authenticated; no new link
- AS / OP
- Code-A bound; no token
- Local callback gate
The RP accepts browser-A/state-A binding to pending tx-A. It prepares a token request to the trusted AS endpoint carrying code-A, the registered redirect and verifier reference; the AS has not yet received it.
- Atlas RP
- Callback accepted; token request ready
- AS / OP
- Code-A bound; no token
- Token request arrived
The ordinary declared TLS token request reached the AS; code and verifier aliases were received, with validation still pending.
- Atlas RP
- Callback accepted
- AS / OP
- Request received; pending
- AS code/verifier gate
The supplied verifier produces S256 matching the code-A challenge. Client/redirect bindings, unused=true and now=1050 < expiresAt=1100 also pass; the AS prepares the token response. This is not yet RP identity acceptance.
- Atlas RP
- Callback accepted
- AS / OP
- Token aliases ready
- AS response received
The AS response reached the RP; this arrival is not marked as identity acceptance or session creation.
- Atlas RP
- Response received; RP pending
- AS / OP
- Token aliases ready
- RP response gate
The RP reads issuer/audience/expiry/key and selected nonce gates for an ordinary token response; an error response establishes no identity.
- Atlas RP
- Identity accepted; mapping pending
- AS / OP
- Token aliases ready
- Local mapping policy
The new iss/sub identity is validated, the linking-session binding is established and the existing account is authenticated. With explicitConfirmation=false, the link remains pending. Identity acceptance does not complete local link approval.
- Atlas RP
- Link confirmation pending
- AS / OP
- Token aliases ready
- Local result
This branch does not reject the new identity; explicit link approval is missing. Account association stays unchanged and the uncommitted link grants no new login authority. Keep the existing authenticated account context separate from a new login session.
- Atlas RP
- No new link; account unchanged
- AS / OP
- Token aliases ready
ACTORS · STATE · DATA FLOW
Which transaction owns a code?
PKCE establishes continuity between an authorization code and its initiating client transaction. For each authorization request the client generates a new high-entropy code_verifier; the S256 challenge is the unpadded base64url encoding of SHA-256 over the ASCII verifier. The authorization request carries the challenge and method; the verifier is used later in the TLS token request. The challenge is not an identity claim, and the code is not an API access token. The AS binds the method and challenge to the issued code.
The token endpoint derives the challenge from the received verifier and compares it with the value associated with the code; a failed comparison yields invalid_grant. Successful PKCE does not remove the other code, client, redirect and lifetime checks. RFC 9700 requires PKCE for public clients and recommends it for confidential clients, while allowing a nonce alternative for confidential OIDC clients with additional precautions. This web profile selects S256 PKCE, state and nonce together; the fixed fixture string is not a verifier for real login use.
Redirect, state and nonce are different fields
The registered redirect is the endpoint to which the AS sends its response. Current BCP requires exact string matching; the native-app localhost redirect-port exception does not transfer to this server-side HTTPS profile. The same host with a different path or added query is not the registered callback string. The callback transaction is associated with browser-A, the selected issuer and pending state-A. A state match concerns callback correlation alone; it does not validate a token signature or user identity by itself.
When a nonce was selected in the authorization request, it must be present in the ID Token and match that transaction’s value. The RP also checks the trusted issuer, its client_id audience, expiry and accepted key/algorithm context. The selected profile verifies a signature with the trusted issuer’s key; the narrow signature alternative for a direct TLS token-endpoint response in OIDC code flow is not a general rule that signatures are unnecessary. PKCE, nonce and state have distinct owners and check times; BCP options for CSRF protection using suitable PKCE/nonce do not make every product use of state and nonce universally mandatory.
Which local account receives the valid identity?
The stable key for a validated OIDC identity is the pair iss and sub. subject-A under another issuer is not automatically the same local user; equal email or preferred_username values are insufficient for account linking. Login through a mapping already established is different from adding a new external identity to an existing local account. The latter is a sensitive account change: the existing account session, authentication of the new identity and the linking transaction binding must all be demonstrated.
The Atlas link-pending row receives a valid ID Token but does not write a mapping because explicit confirmation is missing. That extra confirmation is selected local policy, not a missing token claim. The normal row finds an existing iss/sub mapping and establishes a login session in a separate local operation. The figure starts at callback receipt; it does not certify the earlier browser redirect or actual IdP authentication. The table uses aliases; only the public hash fixture computes the transformation, without a real code/token/JWS exchange.
2. Red · Trust & Impact
Read every scenario without motion
State mismatch
- The received-callback context was selected
The scene starts after the callback reaches the RP. Pending transaction tx-A is bound to browser-A and state-A; callback text does not create its own trust configuration.
- RP transaction/identity context
- Received callback
- RP validation/link gate
- Pending tx-A
- Account/session boundary
- No exchange
- Callback and pending state were compared
The state-denied row matches browserAlias=browser-A but supplies stateAlias=state-B. Correct browser context does not compensate for failed state correlation.
- RP transaction/identity context
- Callback state-B
- RP validation/link gate
- Expected state-A; mismatch
- Account/session boundary
- No exchange
- The RP correlation gate stopped processing
callback=false keeps exchange=false. The RP does not start a token-endpoint request; this is not the AS rejecting a token request.
- RP transaction/identity context
- State mismatch known
- RP validation/link gate
- RP correlation denied
- Account/session boundary
- AS request not started
- Identity validation was not treated as completed
sceneRows preserves identity=false and identityRef=null. Without a token response, issuer/audience/nonce or crypto outcomes are not invented.
- RP transaction/identity context
- State mismatch known
- RP validation/link gate
- No ID validation outcome
- Account/session boundary
- Identity ref=null
- The local-session boundary stayed closed
The supplied action is no-session. PKCE or registered-redirect metadata cannot be used to validate failed RP transaction correlation.
- RP transaction/identity context
- RP callback denied
- RP validation/link gate
- No token exchange
- Account/session boundary
- Action: no-session
- The denial owner was preserved correctly
The known failure boundary is the RP callback gate; the AS token endpoint performed no evaluation here. This outcome does not attribute malicious intent to the browser.
- RP transaction/identity context
- Known: state mismatch
- RP validation/link gate
- AS not contacted
- Account/session boundary
- No local session established
Link unconfirmed
- The new-identity link request was selected
The link-pending row validates new external identity subject-new. Even with an authenticated existing account, a new identity association is a separate sensitive change.
- RP transaction/identity context
- New identity validated
- RP validation/link gate
- Local link pending
- Account/session boundary
- No account mutation
- Iss/sub mapping was read separately
iss=https://identity.example.org and sub=subject-new differ from the existing identity. Matching email or display text is not a sufficient key for a new local mapping.
- RP transaction/identity context
- New iss/sub identity
- RP validation/link gate
- existingIdentityMatch=false
- Account/session boundary
- No account mutation
- The linking-session condition was preserved
existingAccountAuthenticated, newIdentityValidated and linkSessionBound are true. This profile additionally requires explicitConfirmation; a positive identity outcome is not equated with sensitive-change approval.
- RP transaction/identity context
- New identity validated
- RP validation/link gate
- Link session bound; confirmation pending
- Account/session boundary
- No account mutation
- The user-confirmation gate stayed closed
explicitConfirmation=false gives expected=no-link. The approved-link row supplies a separate positive control with confirmation=true; this row is not promoted to that state.
- RP transaction/identity context
- New identity validated
- RP validation/link gate
- Confirmation=false
- Account/session boundary
- No link committed
- Identity acceptance and mutation were separated
Positive callback, exchange and identity gates still produce scene action no-link. Successful S256 or nonce matching does not take over RP local-account policy.
- RP transaction/identity context
- Validated identity-new
- RP validation/link gate
- Account link denied until confirmation
- Account/session boundary
- No local association mutation
- Mapping authority remained with the RP
This result does not reject external identity proof; it withholds the limited local-link action. Future resource permission is not established through an uncommitted link, and no notification-delivery outcome is added.
- RP transaction/identity context
- Known: external identity accepted
- RP validation/link gate
- Known: explicit approval missing
- Account/session boundary
- Known: no account link
ASSUMPTION · BOUNDARY · CONTROL
The effect of the first failed gate
In the state-denied branch the browser context is correct, but state-B does not match pending state-A; the RP does not begin an ordinary token request. Calling this an AS token denial would be incorrect because no request is drawn to the AS. The PKCE-denied branch passes the callback gate and the ordinary token request reaches the AS, but the selected fixture verifier does not match the code challenge, so the AS prepares an error response. Response arrival and local RP handling of the error are separate states.
A successful S256 result does not determine which local account the user receives. If the nonce or issuer/audience gate fails, having received a token response is insufficient to establish a session. Syntactically valid verifier generation is not evidence of entropy either: the public example string exists for reproducible arithmetic. A secret verifier or code/token appearing in logs is a separate credential-handling problem; lesson aliases are not secrets for a real login.
Equal profile attributes do not establish identity
In the email-only row, even a valid identity lacks an existing account session and a linking-session binding, so an email resemblance produces no link. Link-pending separates proof of the new identity from approval of the sensitive local change. If that boundary fails, an external identity may be used in the wrong local account context; which records or tenants it can access still depends on application authorization. Reducing the problem to token format hides its owner: the RP owns local mapping and needs a verifiable binding policy.
3. Blue · Secure development & defense
Read every scenario without motion
S256 calculation
- The public S256 fixture was selected
publicHashFixture supplies the verifier and challenge as a public teaching vector. Its credential=false and randomnessCertified=false boundaries remain explicit.
- Public transaction artifact
- Public noncredential vector
- Contract calculation/review
- Hash calculation not examined
- Evidence scope
- Protocol outcome unknown
- The encoding chain was read
For S256, ASCII bytes are hashed with SHA-256 and the digest is represented as unpadded base64url. This representation chain neither creates entropy nor verifies a user identity.
- Public transaction artifact
- ASCII verifier bytes
- Contract calculation/review
- SHA-256 → base64url without padding
- Evidence scope
- Protocol outcome unknown
- The declared challenge was compared
The fixture declares challenge Z71zbcb356BKqaniSqPvye_RlNZA5-R1eU2rfBz2u0I. It corresponds to fixed public input, not a secret to copy into a production verifier.
- Public transaction artifact
- Public verifier only
- Contract calculation/review
- Declared S256 challenge
- Evidence scope
- No entropy evidence
- The code-binding role was bounded
PKCE provides code-to-verifier continuity at the AS token endpoint. Client authentication, RP state, ID nonce and iss/sub account mapping are different gates.
- Public transaction artifact
- Public hash vector
- Contract calculation/review
- Code/verifier continuity role
- Evidence scope
- Other identity gates separate
- Runtime protocol evidence was separated
The public vector’s hash equality can be recalculated. Correct equality does not establish an actual AS code exchange, random generator or ID-signature verification.
- Public transaction artifact
- Reproducible public arithmetic
- Contract calculation/review
- Protocol execution requires separate evidence
- Evidence scope
- UNKNOWN: actual exchange
- The PKCE conclusion stayed in scope
This artifact explains S256 representation and code-association metadata. It does not establish API sender-constraining of a bearer access token or a local identity binding.
- Public transaction artifact
- Known: public S256 representation
- Contract calculation/review
- Binding roles remain distinct
- Evidence scope
- No broadened authentication claim
Exact redirect
- The registered-redirect contract was selected
The registered string is https://atlas.example.org/login/callback. This web profile has nativeRedirectExceptionSelected=false; other native-app rules are not silently applied.
- Public transaction artifact
- Web redirect profile
- Contract calculation/review
- Exact rows not examined
- Evidence scope
- No transaction conclusion
- The normal matching row was read
A candidate equal to the registered string has expected=true. Destination matching alone does not prove that the current browser transaction is pending or state-bound.
- Public transaction artifact
- Registered candidate exact
- Contract calculation/review
- Redirect expected=true
- Evidence scope
- Callback correlation separate
- Slash and query rows were compared
The callback/ candidate with a trailing slash and the candidate with ?view=notes have expected=false. This fixture uses an exact-string contract; a similar-looking path is not the same registration.
- Public transaction artifact
- Alternate normal candidates
- Contract calculation/review
- Slash/query expected=false
- Evidence scope
- Registration boundary explicit
- Redirect and state roles were separated
The redirect is the registered response destination; state provides selected callback/user-agent correlation. A matching state does not establish acceptance of a different registered destination.
- Public transaction artifact
- Destination role known
- Contract calculation/review
- State correlation separate
- Evidence scope
- No replaced gate
- Nonce and identity mapping remained separate
Nonce supplies selected ID-token transaction binding; iss/sub is the stable local federated-identity key. Equal registered callbacks do not complete these downstream identity gates.
- Public transaction artifact
- Destination role known
- Contract calculation/review
- Nonce/iss-sub separate
- Evidence scope
- Identity acceptance needs separate evidence
- The exact-contract conclusion was scoped
The expected outcomes of the three redirect rows can be explained. This static matrix does not establish actual provider registration, browser navigation or callback receipt.
- Public transaction artifact
- Known: declared exact redirect matrix
- Contract calculation/review
- Native exception not selected
- Evidence scope
- UNKNOWN: provider/navigation runtime
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Build a transaction ledger with clear owners
The RP pending transaction stores the selected issuer, client registration, redirect, user-agent binding, state, nonce and verifier reference together, while preserving each field’s purpose. The AS code ledger binds client, redirect, method/challenge, expiry and one-use conditions. After the local callback gate the token endpoint comes from trusted configuration; incoming callback addresses or key references do not become new trust sources. Token receipt, ID validation and local account mutation are recorded separately.
Regression retains normal login as the positive control: a state mismatch stops the request before the AS, a PKCE mismatch stops token production at the AS, and a nonce mismatch stops identity acceptance at the RP. Rows with a trailing slash or an ordinary added query verify exact callback matching; the native-app exception is not applied here. The S256 check computes ASCII bytes and unpadded base64url output; that arithmetic test does not establish a real random generator’s entropy or protocol compliance.
Treat account linking as a separate sensitive change
Read existing identity mappings through iss/sub; for a new link check the existing account authentication, new identity validation and current linking-session binding again. Atlas additionally requires user confirmation; account storage stays unchanged in link-pending and the prospective link supplies no new login authority. Necessary reauthentication, access removal after unlinking and user notifications are parts of the lifecycle. This fixture shows only a decision ledger: it sends no notification, downloads no key or token and writes no actual local account store.
NOW APPLY IT TO THE CASE
Which local-account approval may PKCE, state and nonce not provide?
The RP callback gate can stop before AS exchange; validating a new identity does not complete explicit link approval. The public S256 vector and redirect matrix explain only their own contracts.
Which issuer/recipient relationship supplies identity in the case? Separate login or delegation success from evidence for current tenant/object permission.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Callback–code–identity binding ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"profile": {
"clientId": "atlas-web",
"issuer": "https://identity.example.org",
"registeredRedirect": "https://atlas.example.org/login/callback",
"flow": "authorization-code",
"method": "S256",
"client": "confidential server-side; authentication declared",
"nativeRedirectExceptionSelected": false,
"sceneStartsAtReceivedCallback": true,
"noActualTokens": true,
"noActualProvider": true
},
"transaction": {
"alias": "tx-A",
"browserAlias": "browser-A",
"stateAlias": "state-A",
"nonceAlias": "nonce-A",
"verifierAlias": "verifier-A",
"pending": true,
"createdFor": "login-or-explicit-link",
"oneUse": true
},
"publicHashFixture": {
"verifier": "atlas-public-pkce-fixture-AAAAAAAAAAAAAAAAAAAAAAAA",
"challenge": "Z71zbcb356BKqaniSqPvye_RlNZA5-R1eU2rfBz2u0I",
"method": "S256",
"encoding": "ASCII -> SHA-256 -> base64url without padding",
"credential": false,
"randomnessCertified": false
},
"codeAssociation": {
"codeAlias": "code-A",
"transactionAlias": "tx-A",
"clientId": "atlas-web",
"redirect": "https://atlas.example.org/login/callback",
"method": "S256",
"challenge": "Z71zbcb356BKqaniSqPvye_RlNZA5-R1eU2rfBz2u0I",
"unused": true,
"now": 1050,
"expiresAt": 1100
},
"redirectRows": [
{
"candidate": "https://atlas.example.org/login/callback",
"expected": true
},
{
"candidate": "https://atlas.example.org/login/callback/",
"expected": false
},
{
"candidate": "https://atlas.example.org/login/callback?view=notes",
"expected": false
}
],
"callbackRows": [
{
"case": "normal",
"browserAlias": "browser-A",
"stateAlias": "state-A",
"pending": true,
"expected": true
},
{
"case": "state-denied",
"browserAlias": "browser-A",
"stateAlias": "state-B",
"pending": true,
"expected": false
},
{
"case": "browser-other",
"browserAlias": "browser-B",
"stateAlias": "state-A",
"pending": true,
"expected": false
},
{
"case": "not-pending",
"browserAlias": "browser-A",
"stateAlias": "state-A",
"pending": false,
"expected": false
}
],
"exchangeRows": [
{
"case": "normal",
"verifier": "atlas-public-pkce-fixture-AAAAAAAAAAAAAAAAAAAAAAAA",
"clientId": "atlas-web",
"redirect": "https://atlas.example.org/login/callback",
"method": "S256",
"unused": true,
"now": 1050,
"expected": true
},
{
"case": "pkce-denied",
"verifier": "atlas-public-pkce-fixture-BBBBBBBBBBBBBBBBBBBBBBBB",
"clientId": "atlas-web",
"redirect": "https://atlas.example.org/login/callback",
"method": "S256",
"unused": true,
"now": 1050,
"expected": false
},
{
"case": "expired",
"verifier": "atlas-public-pkce-fixture-AAAAAAAAAAAAAAAAAAAAAAAA",
"clientId": "atlas-web",
"redirect": "https://atlas.example.org/login/callback",
"method": "S256",
"unused": true,
"now": 1100,
"expected": false
}
],
"identityRows": [
{
"case": "normal",
"nonceAlias": "nonce-A",
"now": 1050,
"expected": true
},
{
"case": "nonce-denied",
"nonceAlias": "nonce-B",
"now": 1050,
"expected": false
},
{
"case": "id-expired",
"nonceAlias": "nonce-A",
"now": 1200,
"expected": false
}
],
"accountRows": [
{
"case": "known-identity",
"iss": "https://identity.example.org",
"sub": "subject-A",
"existingIdentityMatch": true,
"existingAccountAuthenticated": true,
"linkRequested": false,
"newIdentityValidated": true,
"linkSessionBound": true,
"explicitConfirmation": false,
"expected": "existing-login"
},
{
"case": "link-pending",
"iss": "https://identity.example.org",
"sub": "subject-new",
"existingIdentityMatch": false,
"existingAccountAuthenticated": true,
"linkRequested": true,
"newIdentityValidated": true,
"linkSessionBound": true,
"explicitConfirmation": false,
"expected": "no-link"
},
{
"case": "approved-link",
"iss": "https://identity.example.org",
"sub": "subject-new",
"existingIdentityMatch": false,
"existingAccountAuthenticated": true,
"linkRequested": true,
"newIdentityValidated": true,
"linkSessionBound": true,
"explicitConfirmation": true,
"expected": "link"
},
{
"case": "email-only",
"iss": "https://other-id.example.org",
"sub": "subject-A",
"emailMatches": true,
"existingIdentityMatch": false,
"existingAccountAuthenticated": false,
"linkRequested": true,
"newIdentityValidated": true,
"linkSessionBound": false,
"explicitConfirmation": true,
"expected": "no-link"
}
],
"roles": {
"state": "selected callback/user-agent correlation",
"nonce": "selected ID Token transaction binding",
"pkce": "code-to-verifier continuity at AS token endpoint",
"redirect": "registered response destination",
"issSub": "stable local federated identity key"
},
"sceneRows": [
{
"case": "normal",
"callback": true,
"exchange": true,
"identity": true,
"action": "existing-login",
"identityRef": "identity-A"
},
{
"case": "state-denied",
"callback": false,
"exchange": false,
"identity": false,
"action": "no-session",
"identityRef": null
},
{
"case": "pkce-denied",
"callback": true,
"exchange": false,
"identity": false,
"action": "no-session",
"identityRef": null
},
{
"case": "link-pending",
"callback": true,
"exchange": true,
"identity": true,
"action": "no-link",
"identityRef": "identity-new"
}
],
"identityFixtures": {
"identity-A": {
"iss": "https://identity.example.org",
"sub": "subject-A",
"aud": [
"atlas-web"
],
"nonceAlias": "nonce-A",
"exp": 1200,
"signatureValidDeclared": true,
"keyProvenanceDeclared": true,
"registeredAlgorithmAccepted": true,
"additionalUntrustedAudience": false
},
"identity-new": {
"iss": "https://identity.example.org",
"sub": "subject-new",
"aud": [
"atlas-web"
],
"nonceAlias": "nonce-A",
"exp": 1200,
"signatureValidDeclared": true,
"keyProvenanceDeclared": true,
"registeredAlgorithmAccepted": true,
"additionalUntrustedAudience": false
}
},
"identityTemplate": {
"iss": "https://identity.example.org",
"sub": "subject-A",
"aud": [
"atlas-web"
],
"nonceAlias": "nonce-A",
"exp": 1200,
"signatureValidDeclared": true,
"keyProvenanceDeclared": true,
"registeredAlgorithmAccepted": true,
"additionalUntrustedAudience": false
}
}- transaction
- Browser and correlation references in the pending RP transaction.
- publicHashFixture
- Fixed noncredential ASCII fixture and S256 result; no entropy claim.
- codeAssociation
- Method/challenge and other context the AS associates with its code.
- accountRows
- Existing login, approved linking and no-link are separate local decisions.
Aliases are not real codes or tokens. The public S256 fixture covers hashing/encoding only, not entropy, AS/RP, signatures, browser redirects or account storage.
Two reality checks
Passing PKCE means the account is correctly linked.
PKCE checks code/verifier continuity; ID Token validation and local iss/sub mapping/link policy are separate gates.
The same email safely merges two accounts.
Email is not a stable unique identity. The existing account and new identity authentication must be bound to the linking session.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
Callback, exchange and identity gates pass, but the new identity has explicitConfirmation=false. Is the account link created?
- Yes. A validated identity or matching email automatically authorizes linking.
- No. link-pending results in no-link.
Read the explained answer
No. link-pending results in no-link. — The new identity may be validated, but this link policy requires explicit confirmation. Successful login gates do not merge accounts by themselves.
Does adding a trailing slash or query to the registered callback count as a match for this web client?
Selected lesson artifact fields; teaching example
- profile.registeredRedirect
- https://atlas.example.org/login/callback
- profile.nativeRedirectExceptionSelected
- false
- redirectRows
- [{"candidate":"https://atlas.example.org/login/callback","expected":true},{"candidate":"https://atlas.example.org/login/callback/","expected":false},{"candidate":"https://atlas.example.org/login/callback?view=notes","expected":false}]
- No. Only the exact registered URL is true.
- Yes. All callback URLs on the same host are equivalent.
Read the explained answer
No. Only the exact registered URL is true. — callback/ and callback?view=notes are different candidates. With nativeRedirectExceptionSelected=false, no special native-app matching rule is assumed.
What three different bindings do state, nonce and PKCE establish in this transaction? Why is the public S256 fixture not a real login or randomness proof?
Read the explained answer
State correlates the callback with the pending browser transaction. Nonce binds the ID token to this transaction. PKCE checks code-to-verifier continuity at the AS token endpoint without removing other identity or account conditions. The public fixture demonstrates ASCII → SHA-256 → unpadded base64url. It is not a credential or proof of a real provider, token exchange or verifier randomness. The local identity key is iss/sub, not email.
□ I did not interchange state, nonce and PKCE roles.
□ I did not treat the public hash fixture as a real login or entropy test.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- RFC 7636 — Proof Key for Code Exchange§4.1 code verifier syntax and generation · §4.2 S256 · §4.3–4.6 authorization/code association/token verifier gate
- RFC 9700 — Best Current Practice for OAuth 2.0 Security§2.1 exact registered redirect and CSRF choices · §2.1.1 client requirements; transaction/user-agent binding; S256 and AS enforcement
- OpenID Connect Core 1.0 incorporating errata set 2§3.1.3.7 issuer/audience/key/signature/expiry/nonce validation · §5.7 Claim Stability and Uniqueness
- OWASP Authentication Cheat Sheet — federated account linkingSecure Federated Account Linking · Require Re-authentication for Sensitive Features