Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected

ID Token vs JWT: Decode, Validate & Read Claims

Explore the structure of ID tokens and JWTs in OpenID Connect, learn to decode and validate them, and extract user claims for secure authentication.

Focus: id token

Sponsored

Sponsored Reserved space — layout preview until AdSense is connected

ID Tokens and JWT Structure in OpenID Connect: Decoding, Validating, and Reading Claims

What is an ID Token?

An ID token is a JSON Web Token (JWT) that an OpenID Connect (OIDC) provider returns to a client after a successful authentication. It contains self‑contained information about the user (claims) and is signed by the provider so that the client can verify its integrity without contacting the server for every request.

Key points: - ID token = JWT – it follows the same three‑part structure: header.payload.signature. - Claims inside the payload describe who the user is and when the token was issued. - The token is short‑lived (usually a few minutes) and cannot be reused after expiration.

JWT Structure Overview

A JWT is composed of three Base64Url‑encoded sections separated by dots (.):

  1. Header – describes the token type (JWT) and the signing algorithm (e.g., RS256).
  2. Payload – a set of claims (statements about the token). In OIDC the payload always contains the sub claim and may include iss, aud, exp, iat, nonce, and user‑specific attributes.
  3. Signature – the result of signing the header.payload with the provider’s private key (or a symmetric secret). The client verifies this signature with the provider’s public key.

Example (decoded)

Code snippet
Header (base64url): eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
Payload (base64url): eyJ1c2VySWQiOiIxMjM0NSIsImV4cCI6MTcyMzQ1NjU2MSwiaWF0IjoxNjcyMzQ1NjYxLCJzdWIiOiIyMDEyMzQ1Njc4In0=
Signature: rXtX... (truncated)

When decoded, the payload becomes a JSON object:

Code snippet
{
  "sub": "1234567890",
  "iat": 1622547600,
  "exp": 1622551200,
  "nonce": "abcdef123456"
}

Decoding a JWT

The JWT can be decoded manually or with a small helper function. Below is a Python snippet that safely decodes a token without verifying the signature (useful for inspection only).

Code snippet
import base64
import json

def decode_jwt(token: str) -> dict:
    """Return the payload as a dict (no signature verification)."""
    # Split into three parts
    header_b64, payload_b64, _ = token.split('.')
    # Add padding for base64 decoding
    def _b64decode(part: str) -> bytes:
        return base64.urlsafe_b64decode(part + '=' * (-len(part) % 4))
    payload_json = _b64decode(payload_b64).decode('utf-8')
    return json.loads(payload_json)

# Example usage
jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NSIsImV4cCI6MTcyMzQ1NjU2MSwiaWF0IjoxNjcyMzQ1NjYxLCJzdWIiOiIyMDEyMzQ1Njc4In0="
payload = decode_jwt(jwt)
print(payload)

Output

Code snippet
{'sub': '1234567890', 'iat': 1622547600, 'exp': 1622551200, 'nonce': 'abcdef123456'}

Note: Decoding alone does not guarantee the token is trustworthy. Validation (signature, expiry, audience, issuer, nonce) must be performed before using any claim.

Validating an ID Token

1. Verify the Signature

  • Use the provider’s public key (for asymmetric algorithms like RS256) or the secret (for HS256).
  • Libraries such as pyjwt or python-jose handle this automatically.

2. Check Standard Claims

Claim Purpose Typical Validation
iss Issuer identifier Must match the expected OIDC provider URL.
aud Audience Must be the client ID (or a list containing it).
exp Expiration time Token must not be expired (current_time < exp).
iat Issued‑at time Optional, but useful for detecting clock skew.
nonce Proof of possession Must match the nonce sent in the authentication request.

3. Clock Skew Handling

Because servers may have slightly different clocks, allow a small window (e.g., ±30 seconds) when comparing exp and iat.

4. Example Validation (Python)

Code snippet
import jwt  # pyjwt
import requests
import time

# 1. Fetch the provider's JWKS (JSON Web Key Set) – contains public keys
jwks_url = "https://example-idp.com/.well-known/jwks.json"
jwks = requests.get(jwks_url).json()

def get_signing_key(header):
    """Extract the correct key from JWKS based on header."""
    kid = header['kid']
    for key in jwks['keys']:
        if key['kid'] == kid:
            return jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key))
    raise ValueError("Unable to find signing key")

# 2. Decode and verify
def validate_id_token(token: str, client_id: str, nonce: str) -> dict:
    # Decode header to get the key id (kid)
    header = jwt.get_unverified_header(token)
    key = get_signing_key(header)

    # Verify signature, audience, issuer, expiration, and nonce
    claims = jwt.decode(
        token,
        key,
        algorithms=[header['alg']],
        audience=client_id,
        issuer="https://example-idp.com/",
        options={"require": ["exp", "iat", "nonce"]},
        claims_options={"nonce": {"essential": True}}
    )
    # Additional nonce check (the token payload already contains it)
    if claims.get("nonce") != nonce:
        raise ValueError("Invalid nonce")
    return claims

# Usage
client_id = "my-client-123"
nonce = "xyz789"
id_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
payload = validate_id_token(id_token, client_id, nonce)
print(payload)

The function: - Retrieves the public key from the JWKS endpoint. - Validates the signature algorithm matches the header. - Ensures required claims are present. - Checks that the nonce matches the one sent during login.

Reading Claims

Once the token is validated, you can read any claim directly from the payload. Common OIDC claims include:

  • subsubject: unique identifier for the user within the provider.
  • email – user’s email address (if the provider discloses it).
  • name – full name.
  • given_name – first name.
  • family_name – last name.
  • profile – URL to the user’s profile picture.
  • role – optional custom claim for authorization.

Example: Extracting User Info

Code snippet
def extract_user_info(claims: dict) -> dict:
    """Return a simplified user info dict."""
    return {
        "user_id": claims.get("sub"),
        "email": claims.get("email"),
        "name": claims.get("name"),
        "given_name": claims.get("given_name"),
        "family_name": claims.get("family_name"),
    }

# Assuming `payload` is the dict returned from validate_id_token
user_info = extract_user_info(payload)
print(user_info)

Typical output:

Code snippet
{
  "user_id": "1234567890",
  "email": "alice@example.com",
  "name": "Alice Example",
  "given_name": "Alice",
  "family_name": "Example"
}

You can now use user_info to create a session, fetch additional profile data, or perform authorization checks.

Real‑World Example: From Login to Secure Access

  1. User clicks “Login with Google”. The OIDC provider redirects the user to the authorization endpoint with parameters: - response_type=code - client_id=my-client-123 - redirect_uri=https://myapp.com/callback - scope=openid email profile - state=random123 - nonce=xyz789

  2. Provider authenticates the user and redirects back to https://myapp.com/callback?code=abc123&state=xyz789.

  3. Backend exchanges the authorization code for tokens (access token, ID token, optionally refresh token) at the token endpoint.

  4. Backend validates the ID token using the steps shown earlier (signature, iss, aud, exp, nonce). If validation fails, the login is aborted.

  5. After validation, the backend extracts the user’s claims, creates a local session, and can safely trust the user’s identity for subsequent API calls.

  6. For API authorization, the backend may read the scope claim (if present) or map the role claim to internal permissions.

This flow demonstrates why correctly decoding and validating the ID token is crucial: it guarantees that the user is who they claim to be, without exposing the user to forged or replayed tokens.

Summary

  • An ID token is a signed JWT that carries user information and authentication status.
  • It consists of three Base64Url parts: header, payload, and signature.
  • Decoding gives you the raw claims; validation (signature, expiry, audience, issuer, nonce) proves the token’s authenticity.
  • Use a reputable library (e.g., pyjwt) to handle cryptographic verification and claim checks.
  • After validation, read the claims to identify the user, populate a session, or enforce authorization.
  • Real‑world implementations follow the OIDC flow: redirect → code exchange → token receipt → token validation → claim extraction → secure access.

By mastering ID token handling, junior developers can build secure, user‑centric applications that leverage OpenID Connect without reinventing authentication logic.

Sponsored

Sponsored Reserved space — layout preview until AdSense is connected

Sponsored

Sponsored Reserved space — layout preview until AdSense is connected

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.