A3BC — Build digital trust. Together.
Français English
Documentation/OIDC integration/Verify the tokens

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:

  1. Verify the signature with the public keys of the jwks_uri endpoint (given by the discovery).
  2. Verify iss (the discovery issuer), aud (your client_id) and exp.
  3. Verify that nonce is the one of the transaction.
  4. Use sub as 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.