Verify the tokens
Checks to perform on the id_token and how to read the claims.
id_token checks
The id_token is a signed JWT. Before trusting its content:
- Verify the signature with the public keys of the
jwks_uriendpoint (given by the discovery). - Verify
iss(the discovery issuer),aud(yourclient_id) andexp. - Verify that
nonceis the one of the transaction. - Use
subas the user identifier specific to your RP (random or anonymized, depending on your configuration).
{
"iss": "https://oidc-sandbox.a3bc.io/",
"aud": ["e251305f-5db8-11f1-8cb7-c8a3620c309f"],
"sub": "f997cfff165741f98038628f103fedb3",
"nonce": "OEozzDDlfS0qb0uidFVZVIRlBjDXUa",
"iat": 1781853814,
"exp": 1781854414,
"given_name": "JOHN",
"family_name": "DOE",
"birthdate": "1978-03-12"
}
The token also contains technical claims (at_hash, auth_time, jti, sid, rat).
Userinfo
The userinfo endpoint returns, with the access_token, the same information as the id_token.
Missing claims
A requested but unavailable claim is not an error. Check the presence of every claim your service depends on. For verified claims, also look at private:*_verified: it tells how the information was acquired (document, openbanking…).
Verification example
import jwt # PyJWT
jwks = jwt.PyJWKClient(JWKS_URI) # jwks_uri from the discovery
key = jwks.get_signing_key_from_jwt(id_token)
claims = jwt.decode(id_token, key.key, algorithms=["RS256"],
audience=CLIENT_ID, issuer=ISSUER)
assert claims["nonce"] == expected_nonce
import { createRemoteJWKSet, jwtVerify } from "jose";
const jwks = createRemoteJWKSet(new URL(JWKS_URI)); // jwks_uri from the discovery
const { payload } = await jwtVerify(idToken, jwks, { issuer: ISSUER, audience: CLIENT_ID });
if (payload.nonce !== expectedNonce) throw new Error("nonce");
Always read
jwks_uri, issuer and the signing algorithm from the discovery: the values above are examples.