OpenID Connect
OpenID Connect
The identity layer on OAuth 2.0: what the ID token proves, how to validate it, and how to identify users safely.
OpenID Connect (OIDC) is the identity layer on top of OAuth 2.0. OAuth lets a client get an access token for an API; OpenID Connect lets the client learn, in a verifiable way, which user signed in, at which provider, when, and how. It does this with one addition, the ID token, plus a standard UserInfo endpoint and discovery metadata. The core specification is OpenID Connect Core 1.0, currently published incorporating errata set 2 (December 2023).
OIDC uses its own vocabulary for OAuth roles: the client is the Relying Party (RP) and the authorization server is the OpenID Provider (OP).
How it sits on top of OAuth
An OIDC request is an OAuth authorization request with openid in the scope. That scope value is what turns it into an authentication request; without it, OIDC Core says "the behavior is entirely unspecified". The provider authenticates the user and, in addition to any access token, returns an ID token addressed to the client.
scope=openid, a nonce, and an ID token in the token response that the RP must validate before trusting.GET /authorize?response_type=code
&client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
&scope=openid%20email
&state=af0ifjsldkj
&nonce=n-0S6_WzA2Mj
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256 HTTP/1.1
Host: server.example.com
In OIDC the redirect_uri is REQUIRED and MUST exactly match a pre-registered value by simple string comparison (§3.1.2.1). The token response then carries an id_token next to the access token:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": "SlAV32hkKG",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "8xLOxBtZp8",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ..."
}
The ID token
The ID token is a JWT that MUST be signed, and may additionally be encrypted (signed first, then encrypted). The non-normative example from OIDC Core §2:
{
"iss": "https://server.example.com",
"sub": "24400320",
"aud": "s6BhdRkqt3",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"auth_time": 1311280969,
"acr": "urn:mace:incommon:iap:silver"
}
| Claim | Status | Meaning |
|---|---|---|
iss | REQUIRED | Issuer identifier: a case-sensitive https URL with no query or fragment. |
sub | REQUIRED | Subject identifier, "locally unique and never reassigned within the Issuer", at most 255 ASCII characters. |
aud | REQUIRED | MUST contain the RP's client_id; may contain other audiences. |
exp | REQUIRED | Must not be accepted on or after this time. Unrelated to how long the user's session lasts. |
iat | REQUIRED | When the token was issued. |
auth_time | Required if max_age was sent or it was requested as essential; else optional | When the user actually authenticated (which may be long before iat if an existing session was reused). |
nonce | Present if sent in the request | The value from the request, passed through unmodified. If the request had one, the OP MUST include it. |
acr | Optional | Authentication context class the authentication satisfied. Its meaning must be agreed between the parties. |
amr | Optional | Array of authentication method identifiers (for example password plus OTP), preferably from the IANA registry created by RFC 8176. |
azp | Optional | Authorized party. Errata set 2 notes it only occurs with extensions and encourages implementations not using them to ignore it. |
The acr and amr claims are how an RP learns how the user signed in, for example whether a phishing-resistant authenticator was used. They are only as meaningful as the agreement behind them; check the provider's documentation for the values it emits.
ID token validation (§3.1.3.7)
For the authorization code flow, OIDC Core §3.1.3.7 lists thirteen steps. In summary:
- If the ID token is encrypted, decrypt it with the keys and algorithms agreed at registration. If encryption was agreed and the token is not encrypted, the RP SHOULD reject it.
- The issuer identifier (usually from discovery) MUST exactly match
iss. audMUST contain the RP'sclient_id. Reject the token if it does not, or if it lists additional audiences the RP does not trust.- If extensions put an
azpclaim in the token, the RP SHOULD validate it as those extensions specify. - That validation MAY include checking that
azp, when present, is the RP's ownclient_id. - Because the code flow receives the ID token directly from the token endpoint over TLS, server authentication of that TLS connection MAY be used to validate the issuer in place of checking the signature. All other ID tokens' signatures MUST be validated with the issuer's keys.
- The
algSHOULD be RS256 (the default) or the algorithm the client registered. - For MAC algorithms (HS256 and so on), the key is the UTF-8 bytes of the client secret.
- The current time MUST be before
exp. iatcan be used to reject tokens issued too far from the current time; the window is the RP's choice.- If a
noncewas sent, the claim MUST be present and equal to it. The RP SHOULD check for nonce replay. - If
acrwas requested, the RP SHOULD check the value is appropriate. - If
auth_timewas requested (directly or withmax_age), the RP SHOULD check it and request re-authentication if too much time has passed.
If the client uses the nonce as its defense against authorization code injection instead of PKCE (allowed only for confidential clients), RFC 9700 §4.5.3.2 adds two MUSTs: validate the nonce in the ID token from the token endpoint even if another ID token arrived in the authorization response, and use none of the tokens until that check succeeds.
Flows: code, implicit, hybrid
The response_type selects one of three flows (§3):
| Flow | response_type | Tokens from | Status today |
|---|---|---|---|
| Authorization code | code | Token endpoint only | Use this, with PKCE, for all clients. |
| Implicit | id_token, id_token token | Authorization endpoint, in the URL fragment | Avoid. id_token token puts an access token in the front channel, which RFC 9700 §2.1.2 says clients SHOULD NOT do. nonce is REQUIRED here. |
| Hybrid | code id_token, code token, code id_token token | Both | code id_token is acceptable to RFC 9700 because the access token still comes from the token endpoint; variants containing token are not. |
OIDC Core's own comparison table shows why the code flow wins: it is the only flow in which no token is revealed to the user agent, and, like the hybrid flow, it lets the client be authenticated and receive a refresh token. There is no remaining reason for new single-page apps to use the implicit flow; they use the code flow with PKCE, as described on authorization code with PKCE.
The UserInfo endpoint
UserInfo is an OAuth protected resource on the provider that returns claims about the user who authorized the access token (§5.3). It MUST use TLS and accept access tokens as RFC 6750 bearer tokens; GET with the Authorization header is RECOMMENDED.
GET /userinfo HTTP/1.1
Host: server.example.com
Authorization: Bearer SlAV32hkKG
HTTP/1.1 200 OK
Content-Type: application/json
{
"sub": "248289761001",
"name": "Jane Doe",
"email": "janedoe@example.com",
"email_verified": true
}
The sub in the UserInfo response MUST be checked to exactly match the sub in the ID token; if they differ, the response MUST NOT be used (§5.3.2). This guards against token substitution, where the access token belongs to a different user than the ID token.
Discovery and JWKS
An OP publishes its configuration at /.well-known/openid-configuration under its issuer URL (OpenID Connect Discovery 1.0 §4). The document lists the endpoints, supported scopes, response types, signing algorithms, and jwks_uri, which is REQUIRED and MUST use https. Validation rules:
- The
issuerreturned MUST be identical to the URL used as the prefix to fetch the document, and identical to theissin ID tokens from that issuer (Discovery §4.3). If validation fails, the information MUST NOT be used. - Fetch signing keys only from
jwks_uri. OIDC Core says ID tokens SHOULD NOT carryx5u,x5c,jkuorjwkheaders; keys are communicated in advance through discovery and registration. - Cache the JWKS, and handle rotation by refetching when an unknown
kidappears (see JWT).
Identifying users: iss + sub, never email
This is the most consequential rule in OIDC for account security, from §5.7:
Therefore, the only guaranteed unique identifier for a given End-User is the combination of the iss Claim and the sub Claim.
subis unique only within an issuer. Two providers can both issuesub: "24400320"for different people. Key your account table on the pair (iss,sub).- Email, phone number, preferred username and name MUST NOT be used as unique identifiers. An issuer MAY reassign an email address to a different user over time, and a user's email can change.
- Linking a federated login to an existing local account by matching email is how account takeovers happen: someone who controls that address at any accepted issuer, or who can register it unverified, gets the account. If you must link by email, require
email_verified: truefrom an issuer you trust for that domain, and prefer an explicit linking step while the user is signed in to the existing account. - Some providers issue pairwise
subvalues, different per RP, so the same person has different identifiers at different clients (OIDC Core §8). Do not try to correlate them.
Checklist
- Authorization code flow with PKCE and
S256;stateand a per-requestnonce. - Configuration from discovery;
issuerchecked against the URL and againstiss. - ID token validated per §3.1.3.7, with an explicit algorithm allow-list.
- Users keyed on (
iss,sub); no account linking on unverified email. - UserInfo
subcompared with the ID tokensub. - ID tokens never sent to APIs as access tokens; access tokens never treated as proof of login.
acr/amr/auth_timechecked where the application needs a particular strength or freshness of authentication.- After validation, the application creates its own session; the ID token's
expis not the session lifetime.
Sources
- OpenID Connect Core 1.0 incorporating errata set 2, OpenID Foundation. OpenID Foundation specification (cited, brief quotations).
- OpenID Connect Discovery 1.0 incorporating errata set 2, OpenID Foundation. OpenID Foundation specification (cited, brief quotations).
- RFC 9700: Best Current Practice for OAuth 2.0 Security, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 6749: The OAuth 2.0 Authorization Framework, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 7519: JSON Web Token (JWT), IETF. IETF Trust Legal Provisions (brief quotations with attribution).