Skip to content

Understanding ID Tokens — Structure, Claims, and Verification in OIDC

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Understanding ID Tokens. We cover key concepts, practical examples, and best practices to help you master this topic.

An ID token is a signed JWT (JSON Web Token) issued by the OIDC provider that contains verified claims about the authenticated user, including their unique identifier, name, email, and authentication metadata.

What You'll Learn

  • Structure of an ID token as a JWT
  • Standard claims: sub, aud, exp, iat, iss, nonce
  • How to verify ID token signature and claims

Why It Matters

The ID token is the foundation of OIDC authentication. If you cannot correctly validate an ID token, your entire authentication system is vulnerable to forgery, replay attacks, and impersonation.

Real-World Use

When a user authenticates through Doda Browser's OIDC login, the backend receives an ID token. It verifies the signature using the provider's public key, checks the expiration, confirms the audience matches the app, and extracts the user's unique ID for session creation.

flowchart LR
    Provider["OIDC Provider"] -->|"ID Token (JWT)"| App["Your App"]
    App -->|"1. Verify Signature"| JWKS["Provider JWKS"]
    App -->|"2. Check exp, nbf"| Time["Current Time"]
    App -->|"3. Verify aud"| Config["Client ID"]
    App -->|"4. Extract claims"| User["User Identity"]
    style App fill:#dbeafe,stroke:#2563eb

ID Token Structure

An ID token is a JWT with three parts separated by dots: header, payload, signature.

import jwt

# Example decoded ID token payload
id_token_payload = {
    "iss": "https://accounts.google.com",
    "sub": "1234567890",
    "aud": "your-client-id.apps.googleusercontent.com",
    "exp": 1719561600,
    "iat": 1719558000,
    "auth_time": 1719558000,
    "nonce": "random-nonce-value",
    "at_hash": "hash-of-access-token",
    "name": "Alice Smith",
    "email": "alice@example.com",
    "email_verified": True,
    "picture": "https://example.com/avatar.jpg"
}

Standard Claims

Claim Full Name Description
iss Issuer URL of the OIDC provider
sub Subject Unique user identifier (never changes)
aud Audience Client ID the token is intended for
exp Expiration Token expiry timestamp
iat Issued At Token issuance timestamp
auth_time Authentication Time When the user authenticated
nonce Nonce Replay attack prevention
at_hash Access Token Hash Links ID token to access token

ID Token Verification

import requests
import jwt
from jwt import PyJWKClient

def verify_id_token(id_token, client_id, provider_jwks_url):
    # Step 1: Fetch provider's public keys
    jwks_client = PyJWKClient(provider_jwks_url)
    signing_key = jwks_client.get_signing_key_from_jwt(id_token)

    # Step 2: Decode and verify
    claims = jwt.decode(
        id_token,
        signing_key.key,
        algorithms=["RS256"],
        audience=client_id,
        options={
            "verify_exp": True,
            "verify_iat": True,
            "require": ["iss", "sub", "aud", "exp"],
        }
    )
    return claims

# Usage
claims = verify_id_token(
    id_token="eyJhbGciOiJSUzI1NiIs...",
    client_id="your-client-id.apps.googleusercontent.com",
    provider_jwks_url="https://www.googleapis.com/oauth2/v3/certs"
)
print(f"Authenticated: {claims['sub']}")
print(f"Name: {claims.get('name', 'N/A')}")

Understanding the sub Claim

The sub claim is the unique identifier for the user. It never changes for the same user on the same provider. Use this as the primary key for user accounts in your database.

# Store user by sub claim
user_id = claims["sub"]
provider = claims["iss"]

# Check if user exists
user = db.users.find_one({
    "provider": provider,
    "provider_user_id": user_id
})
if not user:
    user = db.users.insert_one({
        "provider": provider,
        "provider_user_id": user_id,
        "name": claims.get("name"),
        "email": claims.get("email"),
    })

Common Mistakes

1. Not Verifying the Signature

Decoding a JWT without verifying the signature is insecure. Anyone can create a JWT with any claims. Always use a verification library.

2. Skipping the aud Check

Without audience verification, a token issued for another app can authenticate users on your app. Always verify aud matches your client ID.

3. Using Decoded Tokens Without Expiry Check

Expired tokens should be rejected. The exp claim must be verified. Libraries check this automatically when verify_exp is enabled.

4. Ignoring nonce for Login Requests

The nonce claim prevents replay attacks. If your auth request included a nonce, verify it matches in the ID token.

5. Assuming All Providers Use the Same Claims

Claims like name, email, and picture are optional. Not all providers include them. Always handle missing claims gracefully.

Practice Questions

  1. What are the three parts of a JWT ID token?
  2. Why is the sub claim important for user identification?
  3. What happens if you do not verify the ID token signature?
  4. Why must the aud claim be verified?
  5. What is the purpose of the nonce claim?

Answers:

  1. Header (algorithm and key ID), payload (claims), signature (signed by the provider).
  2. The sub is a stable, unique identifier for the user that never changes, unlike email or name.
  3. An attacker can forge a JWT with any user identity, gaining unauthorized access to any account.
  4. Without audience verification, a token issued for a different application can be used to authenticate on your app.
  5. The nonce protects against replay attacks by ensuring the token corresponds to a specific authentication request.

Challenge: Write a Python function that fully validates an ID token: verify the signature using JWKS, check exp, aud, iss, and nonce. Include proper error handling for each validation step.

FAQ

What algorithm is typically used to sign ID tokens?

: RS256 (RSA with SHA-256) is the most common. Some providers use ES256 (ECDSA). The algorithm is specified in the JWT header.

Can an ID token be used as an API access token?

: No. ID tokens are for authentication only. Use access tokens for API authorization.

How long is an ID token typically valid?

: Usually 1 hour. The exp claim specifies the exact expiration time. Never accept tokens past this time.

What happens if the signing key rotates?

: Fetch the current keys from the JWKS endpoint. The provider includes the kid (key ID) in the JWT header to indicate which key was used.

Can an ID token be revoked?

: No, JWT tokens cannot be revoked before expiration. Use short expiration times and implement a blocklist if needed.

Mini Project

Build a Python script that fetches the JWKS from an OIDC provider, verifies an ID token, and extracts all standard claims. Handle verification errors with specific error messages for each failure case.

What's Next

Continue with ID Token Claims Reference for a complete guide to standard and custom claims, or explore UserInfo Endpoint for retrieving additional user data.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro