Hacker DirectoryHacker Directory

WEB / WEB-15.S01

PKCE, redirects and identity binding

Separate code continuity, callback correlation and local account binding.

CORE · RED · BLUE · ARTIFACT · PRACTICE

1. Core · Mechanism

Read every scenario without motion

Normal login

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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

  1. 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
  2. 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
  3. 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
  4. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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?

  1. Yes. A validated identity or matching email automatically authorizes linking.
  2. 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}]
  1. No. Only the exact registered URL is true.
  2. 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