ID token
What the ID token proves, every claim it can carry, and the exact steps to verify it before you start a session.
On this page
The ID token is a signed statement that a person signed in, to your client, just now. It is for your application only. It is not a credential to present to an API: use the access token for that.
What it is#
A JWT with header typ: JWT, an alg of EdDSA or ES256 and a kid. Identity returns it from the token endpoint on the code grant when the openid scope was granted. A refresh response normally carries no ID token, so do not depend on one.
Its lifetime is the same as the access token's, at most 15 minutes (an operator can lower that ceiling). It is a record of an authentication that already happened, so a long life would add nothing.
Claims#
| Claim | Meaning |
|---|---|
iss | The issuer. Must equal the issuer you configured. |
sub | The person's stable identifier. Key accounts on this. For a client registered with the pairwise subject type it is a value specific to that client's sector, not the person's real identifier. |
aud | Your client id, as a single string. |
exp, iat | Expiry and issue time, seconds since the epoch. |
auth_time | When the person last authenticated. |
nonce | The value you pushed. Present when you sent one, which is always. |
acr | The assurance level reached: aal1, aal2 or aal3. See acr and amr values. |
amr | The methods used. |
sid | The identity session. Needed to match a back-channel logout. |
ver | The claim-set version of identity's tokens. Currently 2. |
tid, tslug | The organization and its slug, when the sign-in was for an organization. |
| Profile claims | name, locale, zoneinfo, preferred_username, picture and updated_at with the profile scope. email and email_verified with the email scope. A claim the person has not set is left out. A pairwise client gets no picture. |
There is no azp, no at_hash and no roles. Every claim is defined in the claims reference.
Verify it#
Do every step. Skipping one is how sign-in bugs become account takeovers. The SDK performs all of them in handleCallback, and rp.verifyIdToken repeats them for a stored token.
- Size and shape. Refuse a value that is not a compact JWS, or is unreasonably large. The SDK refuses more than 16 KiB.
typisJWT. An access token (at+jwt) or a logout token (logout+jwt) must never pass as an ID token.algis on your allow-list, and is the algorithm you import the key under. Take the allow-list from your own configuration, never from the token. The SDK default isEdDSA.kidis present. Find that key in the JWKS. If it is not there, refetch once.- The signature verifies with that key.
issequals your configured issuer exactly.audequals your client id. If it is a list,azpmust equal your client id as well.expis in the future andiatis not in the future, within a small clock tolerance. The SDK allows 60 seconds.submust be present and non-empty.nonceequals the one you stored for this browser's transaction.auth_timeis right. If you pushedmax_age,auth_timeis required and must not be older thanmax_ageplus tolerance. It must not be in the future.acrreaches what you asked for. If you pushedacr_values, the token's level must be at least the lowest you listed.
Only after all of that, create your own session from sub.
Worked example#
Decoded, the payload of a verified ID token looks like this.
{ "iss": "https://identity.tdacorp.in", "sub": "0194f2b6-7c1e-7a30-9d4e-2f6f0f3b8c11", "aud": "tdaid_c_example", "exp": 1790000600, "iat": 1790000000, "auth_time": 1789999950, "nonce": "n-0S6_WzA2Mj", "acr": "aal2", "amr": ["hwk", "user"], "sid": "0194f2b6-0000-7000-8000-000000000001", "ver": 2, "name": "Asha Rao", "email": "asha@example.com", "email_verified": true}The values are illustrative. With the SDK:
const { tokens, idToken } = await rp.handleCallback(new URL(request.url), transaction)// idToken.sub, idToken.sid, idToken.acr, idToken.amr, idToken.authTimeErrors#
The SDK throws an IdTokenError whose reason names the failed step: typ, alg, kid_missing, signature, iss, aud, azp, exp, iat, sub, nonce, auth_time, max_age, acr, malformed, or a key-set reason when the key could not be found or the set could not be fetched. When verification fails, no tokens are returned to you, and the refresh token the exchange produced is revoked.
Security notes#
- Never skip
nonce. It ties the token to the browser that started the login. Your stored transaction must be tamper-proof or the check proves nothing. - Do not send an ID token to an API. Its audience is your client, not the API. A resource server must refuse it, and yours will.
- Keep it for sign-out. The ID token is the
id_token_hintfor end session. An expired one still works as a hint. - Do not treat claims as current. An ID token is a snapshot at sign-in. For a fresher profile use userinfo.