Hacker DirectoryHacker Directory

WEB / WEB-15.02

OIDC, ID token and SSO

Two Atlas applications logging in through the same OP create separate RP sessions. The ID token’s recipient and key provenance are central to this distinction.

CORE · RED · BLUE · ARTIFACT · PRACTICE

1. Core · Mechanism

Read every scenario without motion

B session created

  1. SSO context

    The OP session and Atlas A session are already valid; B does not yet have its own session.

    Browser
    A session alias; no B
    OpenID Provider
    OP session valid; response ready
    Atlas A · RP
    A session valid
    Atlas B · RP
    No B session; transaction bound
  2. B received token

    The token response from the previously completed code exchange arrived at B; authentication validation is pending.

    Browser
    A session alias; no B
    OpenID Provider
    OP session valid; response ready
    Atlas A · RP
    A session valid
    Atlas B · RP
    ID alias received; validation pending
  3. B ID gate

    For B, aud=[atlas-rp-b] matches and the issuer and trusted key source are accepted. The declared signature result, transaction nonce and now=1050 < exp=2000 also satisfy the selected profile. B accepts the ID token but has not yet created its session.

    Browser
    A session alias; no B
    OpenID Provider
    OP session valid; response ready
    Atlas A · RP
    A session valid
    Atlas B · RP
    ID accepted; no session yet
  4. B local session

    After the ID gate, B creates its own session using issuer/subject binding; it changes neither A nor OP session.

    Browser
    A session alias; no B
    OpenID Provider
    OP session valid; response ready
    Atlas A · RP
    A session valid
    Atlas B · RP
    B session created locally
  5. Browser receipt

    B’s local outcome arrived at the browser; A’s session and the OP authentication context remain independent.

    Browser
    B session alias received
    OpenID Provider
    OP session valid; response ready
    Atlas A · RP
    A session valid
    Atlas B · RP
    B session created locally

Wrong audience

  1. SSO context

    The OP session and Atlas A session are already valid; B does not yet have its own session.

    Browser
    A session alias; no B
    OpenID Provider
    OP session valid; response ready
    Atlas A · RP
    A session valid
    Atlas B · RP
    No B session; transaction bound
  2. B received token

    The token response from the previously completed code exchange arrived at B; authentication validation is pending.

    Browser
    A session alias; no B
    OpenID Provider
    OP session valid; response ready
    Atlas A · RP
    A session valid
    Atlas B · RP
    ID alias received; validation pending
  3. B ID gate

    The received ID token has aud=[atlas-rp-a], but B accepts tokens for clientId=atlas-rp-b. Positive signature, nonce and time do not repair the audience mismatch. ID acceptance is denied; B creates no new local identity or session.

    Browser
    A session alias; no B
    OpenID Provider
    OP session valid; response ready
    Atlas A · RP
    A session valid
    Atlas B · RP
    ID audience denied; no session
  4. Browser receipt

    B’s local outcome arrived at the browser; A’s session and the OP authentication context remain independent.

    Browser
    B login error received; no B cookie
    OpenID Provider
    OP session valid; response ready
    Atlas A · RP
    A session valid
    Atlas B · RP
    ID audience denied; no session

OP session expired

  1. A and B are separate

    The browser holds A’s alias but no B session; the OP session has independently expired.

    Browser
    A alias; no B; prompt=none
    OpenID Provider
    OP session expired
    Atlas A · RP
    A session valid
    Atlas B · RP
    No B session
  2. OP received request

    A normal prompt=none authentication request arrived at the OP; its session gate has not yet decided.

    Browser
    A alias; no B; prompt=none
    OpenID Provider
    prompt=none received; session pending
    Atlas A · RP
    A session valid
    Atlas B · RP
    No B session
  3. OP requires login

    The selected OP policy cannot authenticate silently and produces login_required; it does not change RP A.

    Browser
    A alias; no B; prompt=none
    OpenID Provider
    login_required selected; no ID token
    Atlas A · RP
    A session valid
    Atlas B · RP
    No B session
  4. Browser received error

    The OP error response arrived at the browser; no B session is created and A’s prior session remains valid.

    Browser
    OP error received; no B session
    OpenID Provider
    login_required selected; no ID token
    Atlas A · RP
    A session valid
    Atlas B · RP
    No B session

ACTORS · STATE · DATA FLOW

Authentication information on top of OAuth

OIDC adds standardized End-User authentication information to an OAuth flow. The client becomes a Relying Party and the identity authority an OpenID Provider; the broader term IdP can also describe non-OIDC federation systems. In our code flow, openid scope marks an OIDC request. The token response carries different tools: an ID token describes authentication to the RP, while an access token is used at an appropriate resource endpoint. If UserInfo is used, its credential is the access token; we do not use the ID token as a Notes API bearer credential.

Mina visits Atlas A and then Atlas B. An OP session that authenticated Mina can help the second authentication finish without another password screen. B still starts its own transaction, validates an ID token for its own client_id audience and creates its own session. OP-to-B information does not transfer A’s cookie to B. Consent, authentication freshness or OP policy can require interaction; SSO does not promise silent login in every situation.

Reading claims is not validating them

The artifact selects iss=https://id.example.org, sub=subject-Mina and aud=[atlas-rp-b]. The stable local user key is the issuer/subject pair; display name or email does not carry that guarantee. The RP exactly compares its pretrusted issuer, checks that its client_id is an audience and that additional audiences are acceptable. The zero-leeway expiration example requires now<exp. If the authentication request sent a nonce, the ID token must contain the matching transaction nonce; this is not generalized into an independent nonce for every new token in every OIDC grant.

The selected RP profile checks signatures using registered RS256 and a signing key from the issuer’s trusted metadata JWKS. The token’s kid can help select a key; it does not establish a trustworthy key source. The Discovery issuer, the issuer used to retrieve configuration and the ID token iss match in the same context. Key changes preserve that trusted issuer source; a key URL supplied by a token is not a trust anchor. The artifact contains no actual JWT/JWK: key provenance and signature results are declared fields, explaining cryptographic conditions without claiming to execute them.

Core §3.1.3.7 permits TLS server validation to establish the issuer in place of a signature check for an ID token received directly from the token endpoint in code flow. This lesson selects a stricter signature-check profile without erasing that exception across OIDC contexts. Algorithm choice, extension-specific azp, a local iat acceptance range, requested acr and auth_time/max_age also require profile-aware handling. Selecting a single audience does not claim that all other profile details were actually validated.

2. Red · Trust & Impact

Read every scenario without motion

Different RP

  1. The RP authentication profile was selected

    Atlas B accepts ID tokens for clientId=atlas-rp-b and issuer=https://id.example.org. The ID-token recipient is RP authentication; notes-api resource access uses a separate access-token type.

    ID token profile
    audience-denied
    RP validation
    RP gate not read
    Local session boundary
    No RP B session
  2. Signature and context were separated

    signatureValidDeclared=true is supplied. Even with registeredAlgorithm RS256, this predicate does not by itself complete key provenance and recipient checks.

    ID token profile
    audience-denied
    RP validation
    Signature predicate=true
    Local session boundary
    No RP B session
  3. The RP’s failed gate was preserved

    aud=[atlas-rp-a] gives audienceMatches=false. An assertion for A is not authentication acceptance at B; decoding does not change that recipient boundary.

    ID token profile
    audience-denied
    RP validation
    Audience A ≠ RP B
    Local session boundary
    No RP B session
  4. Positive nonce and time were not treated as sufficient

    The nonce matches and now=1050 lies before exp=2000 in this case. These positive conditions do not compensate for failed audience.

    ID token profile
    audience-denied
    RP validation
    Nonce/time valid; ID rejected
    Local session boundary
    No RP B session
  5. Local identity acceptance was withheld

    Expected ID acceptance=false. RP B does not establish new authenticated local context or a session from the readable sub=subject-Mina claim.

    ID token profile
    audience-denied
    RP validation
    ID acceptance=false
    Local session boundary
    No new RP session established
  6. Authentication and API permission remained separate

    RP authentication denial is this case’s outcome. The OP session or Atlas A session is not automatically deleted; notes-api token and object-policy outcomes are not inferred from this denial.

    ID token profile
    audience-denied
    RP validation
    Known: RP B ID rejection
    Local session boundary
    Other session/API state separate

Key provenance

  1. The RP authentication profile was selected

    Atlas B accepts ID tokens for clientId=atlas-rp-b and issuer=https://id.example.org. The ID-token recipient is RP authentication; notes-api resource access uses a separate access-token type.

    ID token profile
    untrusted-key
    RP validation
    RP gate not read
    Local session boundary
    No RP B session
  2. Signature and context were separated

    signatureValidDeclared=true is supplied. Even with registeredAlgorithm RS256, this predicate does not by itself complete key provenance and recipient checks.

    ID token profile
    untrusted-key
    RP validation
    Signature predicate=true
    Local session boundary
    No RP B session
  3. The RP’s failed gate was preserved

    keyProvenance=false. Seeing the expected kid alias or matching the issuer claim does not establish a trusted signing-key source.

    ID token profile
    untrusted-key
    RP validation
    Key provenance=false
    Local session boundary
    No RP B session
  4. Positive nonce and time were not treated as sufficient

    The nonce matches and now=1050 lies before exp=2000 in this case. These positive conditions do not compensate for failed key provenance.

    ID token profile
    untrusted-key
    RP validation
    Nonce/time valid; ID rejected
    Local session boundary
    No RP B session
  5. Local identity acceptance was withheld

    Expected ID acceptance=false. RP B does not establish new authenticated local context or a session from the readable sub=subject-Mina claim.

    ID token profile
    untrusted-key
    RP validation
    ID acceptance=false
    Local session boundary
    No new RP session established
  6. Authentication and API permission remained separate

    RP authentication denial is this case’s outcome. The OP session or Atlas A session is not automatically deleted; notes-api token and object-policy outcomes are not inferred from this denial.

    ID token profile
    untrusted-key
    RP validation
    Known: RP B ID rejection
    Local session boundary
    Other session/API state separate

ASSUMPTION · BOUNDARY · CONTROL

When an authentication result is used at the wrong boundary

If B receives a token whose audience is Atlas A, even an assumed valid signature does not allow authentication at B. The audience-denied branch shows the boundary of an assertion intended for another application; reading its claims does not remove it. In untrusted-key, even an expected kid alias cannot repair failed issuer/key provenance. SignatureValidDeclared=true cannot replace that missing link. Assess which incorrect condition could reach local user binding and RP session creation; “a JWT arrived” is insufficient.

In the OP-session-expired branch, prompt=none reaches the OP but selected policy returns login_required. Atlas A’s previously created session remains valid in the model, while B has none. Keeping these three states separate makes the boundary visible. Logging out of A likewise does not automatically delete OP and B sessions without an implemented federated logout mechanism. No logout protocol runs here; the lesson explains independent lifetime and invalidation ownership.

Resource policy after identity

Deriving a local user from an ID token does not determine which tenant record that user may access. Access token validation and resource authorization continue separately at the API. Using email directly as an account-join key silently introduces a business rule beyond issuer/subject binding. Account linking, organization membership and authentication strength need their own policy and evidence. This lesson examines what valid authentication information does not say and which data decision an incorrect equivalence would affect.

3. Blue · Secure development & defense

Read every scenario without motion

RP B established

  1. The SSO session record was selected

    This case shows OP session, RP A session and RP B before/after state as separate fields. SSO is neither one global cookie nor a shared session record.

    SSO observation
    case=normal
    OP/RP session review
    Session owners not read
    Session conclusion
    RP B outcome not examined
  2. Initial local sessions were read

    RP A is true and RP B before is false. An active OP session lets the OP evaluate its policy without necessarily repeating authentication.

    SSO observation
    case=normal
    OP/RP session review
    RP A=true; RP B before=false
    Session conclusion
    OP session=true
  3. OP and RP acceptance were separated

    Normal ID validation acceptance belongs to RP B. Even with OP state true, the RP applies issuer/audience/nonce/key/time gates under its own profile.

    SSO observation
    case=normal
    OP/RP session review
    ID accepted=true
    Session conclusion
    RP B outcome under review
  4. The RP B transition observation was read

    rpBAfter=true records local-session establishment after normal ID acceptance. The RP’s session lifetime is not automatically equal to ID-token expiry.

    SSO observation
    case=normal
    OP/RP session review
    RP A=true; RP B after=true
    Session conclusion
    Local lifetimes separate
  5. No logout-propagation evidence was added

    logoutProtocolImplemented=false bounds the profile. Logout at A, OP expiry or browser cleanup does not automatically establish propagation to other session states.

    SSO observation
    case=normal
    OP/RP session review
    Session ownership separate
    Session conclusion
    UNKNOWN: federated logout propagation
  6. The SSO conclusion stayed within session scope

    This row describes normal RP B creation; it is not an actual browser-cookie acceptance or notes-api resource-authorization outcome.

    SSO observation
    case=normal
    OP/RP session review
    Known: independent RP states
    Session conclusion
    RP B created

OP expired

  1. The SSO session record was selected

    This case shows OP session, RP A session and RP B before/after state as separate fields. SSO is neither one global cookie nor a shared session record.

    SSO observation
    case=op-expired
    OP/RP session review
    Session owners not read
    Session conclusion
    RP B outcome not examined
  2. Initial local sessions were read

    RP A is true and RP B before is false. The OP session is false while RP A’s previously established session remains true.

    SSO observation
    case=op-expired
    OP/RP session review
    RP A=true; RP B before=false
    Session conclusion
    OP session=false
  3. OP and RP acceptance were separated

    For prompt=none the OP returns login_required. This does not mean the password is wrong; OP state is insufficient for silent authentication.

    SSO observation
    case=op-expired
    OP/RP session review
    OP decision=login_required
    Session conclusion
    RP B outcome under review
  4. The RP B transition observation was read

    idAccepted=false and rpBAfter=false remain. OP expiry has not simultaneously invalidated RP A’s session.

    SSO observation
    case=op-expired
    OP/RP session review
    RP A=true; RP B after=false
    Session conclusion
    Local lifetimes separate
  5. No logout-propagation evidence was added

    logoutProtocolImplemented=false bounds the profile. Logout at A, OP expiry or browser cleanup does not automatically establish propagation to other session states.

    SSO observation
    case=op-expired
    OP/RP session review
    Session ownership separate
    Session conclusion
    UNKNOWN: federated logout propagation
  6. The SSO conclusion stayed within session scope

    This row describes silent SSO stopping at the OP; RP A is known and no new RP B session exists. No browser-privacy cause or termination of every user session is added.

    SSO observation
    case=op-expired
    OP/RP session review
    Known: independent RP states
    Session conclusion
    No new RP B session

IMPLEMENTATION · SECURITY CONTROL · VERIFICATION

The RP owns its gates and session

Atlas B first fixes the issuer, client registration and accepted algorithm in configuration, preserving metadata issuer matching and signing-key provenance. Whether the transaction sent a nonce is explicit in the validation plan. Server-side validation rather than a UI that merely decodes the token feeds local identity binding. Normal, wrong-audience, expiry-equality, nonce-mismatch and untrusted-key rows exercise the same gate conjunction. The signature field is a selected synthetic result, not a replacement for a real validator test.

The RP creates no session before validation completes. The resulting session follows its own lifetime, rotation and invalidation policy; ID token exp is not automatically cookie lifetime. The OP can require renewed authentication while an RP continues under its own existing session policy, and the reverse can occur. Explicitly design max_age/auth_time conditions for operations requiring fresh authentication. Consent, account selection or browser cookie policy stopping silent SSO is a normal branch, not always an incorrect password.

Evidence for OP and RP sessions

Evidence distinguishes OP response received, RP validation accepted and RP session created. Independence of A and B sessions is checked in both normal SSO and OP-expired branches. If UserInfo is added, its path-specific conditions, including matching its sub to the ID token sub, are applied separately; the fixture does not call that endpoint. Completing this scene does not verify a real OP, JWS library, key rotation, federated logout or browser third-party-cookie behavior.

NOW APPLY IT TO THE CASE

Which local state does ID-token acceptance establish?

Atlas A and B manage their own sessions. RP B recipient and key-provenance gates remain separate from OP state and API permission.

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

Atlas A/B identity and session matrix

Select a field and examine what it supports and where the evidence ends.

Read the raw artifact and field explanations
{
  "profile": {
    "flow": "code",
    "scope": [
      "openid",
      "notes:read"
    ],
    "issuer": "https://id.example.org",
    "clientId": "atlas-rp-b",
    "registeredAlgorithm": "RS256",
    "signatureCheckSelected": true,
    "nonceSent": true,
    "clockLeeway": 0,
    "actualJwtIncluded": false,
    "actualSignatureVerification": false,
    "logoutProtocolImplemented": false
  },
  "idToken": {
    "alias": "id-B",
    "iss": "https://id.example.org",
    "sub": "subject-Mina",
    "aud": [
      "atlas-rp-b"
    ],
    "exp": 2000,
    "iat": 1000,
    "nonceAlias": "nonce-B",
    "kidAlias": "issuer-signing-B"
  },
  "trustedKeys": {
    "configuredIssuer": "https://id.example.org",
    "metadataIssuer": "https://id.example.org",
    "jwksUri": "https://id.example.org/keys",
    "provenanceAccepted": true,
    "signatureValidDeclared": true,
    "algorithm": "RS256"
  },
  "validationCases": [
    {
      "name": "normal",
      "issuerMatches": true,
      "audienceMatches": true,
      "keyProvenance": true,
      "signatureValidDeclared": true,
      "algorithmAccepted": true,
      "nonceMatches": true,
      "now": 1050,
      "expected": true,
      "iss": "https://id.example.org",
      "aud": [
        "atlas-rp-b"
      ],
      "nonceAlias": "nonce-B",
      "algorithm": "RS256"
    },
    {
      "name": "audience-denied",
      "issuerMatches": true,
      "audienceMatches": false,
      "keyProvenance": true,
      "signatureValidDeclared": true,
      "algorithmAccepted": true,
      "nonceMatches": true,
      "now": 1050,
      "expected": false,
      "iss": "https://id.example.org",
      "aud": [
        "atlas-rp-a"
      ],
      "nonceAlias": "nonce-B",
      "algorithm": "RS256"
    },
    {
      "name": "expiry-equality",
      "issuerMatches": true,
      "audienceMatches": true,
      "keyProvenance": true,
      "signatureValidDeclared": true,
      "algorithmAccepted": true,
      "nonceMatches": true,
      "now": 2000,
      "expected": false,
      "iss": "https://id.example.org",
      "aud": [
        "atlas-rp-b"
      ],
      "nonceAlias": "nonce-B",
      "algorithm": "RS256"
    },
    {
      "name": "nonce-mismatch",
      "issuerMatches": true,
      "audienceMatches": true,
      "keyProvenance": true,
      "signatureValidDeclared": true,
      "algorithmAccepted": true,
      "nonceMatches": false,
      "now": 1050,
      "expected": false,
      "iss": "https://id.example.org",
      "aud": [
        "atlas-rp-b"
      ],
      "nonceAlias": "different-transaction",
      "algorithm": "RS256"
    },
    {
      "name": "untrusted-key",
      "issuerMatches": true,
      "audienceMatches": true,
      "keyProvenance": false,
      "signatureValidDeclared": true,
      "algorithmAccepted": true,
      "nonceMatches": true,
      "now": 1050,
      "expected": false,
      "iss": "https://id.example.org",
      "aud": [
        "atlas-rp-b"
      ],
      "nonceAlias": "nonce-B",
      "algorithm": "RS256"
    }
  ],
  "sessions": [
    {
      "case": "normal",
      "opSession": true,
      "rpA": true,
      "rpBBefore": false,
      "idAccepted": true,
      "rpBAfter": true
    },
    {
      "case": "audience-denied",
      "opSession": true,
      "rpA": true,
      "rpBBefore": false,
      "idAccepted": false,
      "rpBAfter": false
    },
    {
      "case": "op-expired",
      "opSession": false,
      "rpA": true,
      "rpBBefore": false,
      "prompt": "none",
      "opDecision": "login_required",
      "idAccepted": false,
      "rpBAfter": false
    }
  ],
  "tokenRecipients": {
    "id-B": "atlas-rp-b authentication",
    "access-B": "notes-api resource access",
    "session-B": "atlas-rp-b local session lookup"
  },
  "omitted": [
    "actual code exchange",
    "actual JWT/JWS/JWKS bytes",
    "UserInfo call",
    "azp extensions",
    "acr and max_age requested policies",
    "federated logout",
    "browser third-party-cookie policy",
    "iat freshness and client-specific accepted range"
  ]
}
idToken
Authentication claim aliases; stable identity is the iss/sub pair.
trustedKeys
Selected issuer metadata and key provenance, separate from kid.
validationCases
Gates are jointly necessary; not an actual crypto result.
sessions
OP, A and B session ownership is independent.
tokenRecipients
Different recipients for ID token, access token and local session.

A selected single-audience RS256 profile; signatureValidDeclared is a fixture condition. No real JWT decode/JWS, metadata fetch, cookie or OP runs.

Two reality checks

Logging in to A creates B’s session too.

An OP session can ease repeated authentication; B creates its own transaction, validation and session.

A matching kid makes the key trusted.

kid only helps selection; a verified issuer source and an accepted algorithm are separately necessary.

YOUR TURN

Work through this story.

Predict the outcome, find the evidence boundary, and explain it in your own words.

ID-token signatureValidDeclared is true but keyProvenance=false. May an RP-B session be established?

  1. Yes; the key origin is irrelevant when signature is true.
  2. No; the selected untrusted-key case is rejected.
Read the explained answer

No; the selected untrusted-key case is rejected. — A signature condition does not replace trusted issuer/JWKS provenance; the validation conjunction is incomplete.

The OP expired while RP-A is active; prompt none returns login_required. Does RP-A automatically log out?

Selected lesson artifact fields; teaching example

sessions[2]
{"case":"op-expired","opSession":false,"rpA":true,"rpBBefore":false,"prompt":"none","opDecision":"login_required","idAccepted":false,"rpBAfter":false}
  1. No; supplied RP-A state stays true and RP-B is not established.
  2. Yes; every RP session is automatically deleted when the OP session ends.
Read the explained answer

No; supplied RP-A state stays true and RP-B is not established. — RP-A’s independent session remains active in this record. The prompt=none request cannot establish a new RP-B session because the OP requires interaction. This does not predict failure of a later interactive authentication flow.

ID-B targets RP-B while access-B targets notes-api. Why should the ID token not serve as API authorization? Also explain nonce.

Read the explained answer

The ID token serves the RP’s validation of OP authentication; issuer/audience/time/signature and a sent nonce are evaluated against the transaction. An access token belongs to RS resource context; local-session lookup is another carrier. RP audience in an ID token is neither an API grant nor object permission.

□ I distinguished ID/access/session recipients.

□ I tied nonce to applicable transaction binding.

Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples

Sources and scope

  • OpenID Connect Core 1.0 — Final incorporating errata set 2§1 Introduction and terminology; §2 ID Token · §3.1.2.1 Authentication Request: openid, prompt, nonce, max_age · §3.1.2.6 Authentication Error Response · §3.1.3.2 Token Request Validation; §3.1.3.5 Token Response Validation · §3.1.3.7 ID Token Validation; §3.1.3.8 Access Token Validation · §5.7 Claim Stability and Uniqueness · §5.3.1 UserInfo Request; §5.3.2 Successful UserInfo Response
  • OpenID Connect Discovery 1.0 — Final incorporating errata set 2§3 OpenID Provider Metadata: issuer, jwks_uri · §4.3 OpenID Provider Configuration Validation
  • RFC 9700 — Best Current Practice for OAuth 2.0 Security§2.1 Protecting Redirect-Based Flows; §2.1.1 Authorization Code Grant · §2.1.2 Implicit Grant; §2.2.1 Access Tokens; §2.2.2 Refresh Tokens · §2.3 Access Token Privilege Restriction; §2.4 Resource Owner Password Credentials Grant; §2.5 Client Authentication
  • OWASP Session Management Cheat SheetSameSite Attribute; Domain and Path Attributes · Session Expiration / Idle Timeout; Absolute Timeout · Renew the Session ID After Any Privilege Level Change
  • OWASP Authorization Cheat SheetRecommendations / Enforce Least Privileges · Recommendations / Deny by Default; Validate the Permissions on Every Request