How to Create and Verify an OpenID Connect ID Token in Python

Generate and validate a mock OpenID Connect ID token (JWT) with HS256 signing using only the Python standard library.

Medium Python 3.9+ Aug 9, 2026 Auth & security at scale 14 views 0 copies

Python code

75 lines
Python 3.9+
import base64
import hashlib
import hmac
import json
import time
from typing import Optional


def b64url_encode(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode("utf-8")


def b64url_decode(data: str) -> bytes:
    padding = "=" * (-len(data) % 4)
    return base64.urlsafe_b64decode(data + padding)


def create_id_token(
    client_id: str,
    issuer: str,
    subject: str,
    secret: str,
    email: Optional[str] = None,
    expiry_seconds: int = 3600,
) -> str:
    header = {"alg": "HS256", "typ": "JWT"}

    now = int(time.time())
    payload = {
        "iss": issuer,
        "sub": subject,
        "aud": client_id,
        "iat": now,
        "exp": now + expiry_seconds,
    }
    if email:
        payload["email"] = email

    header_segment = b64url_encode(json.dumps(header, separators=(",", ":")).encode())
    payload_segment = b64url_encode(json.dumps(payload, separators=(",", ":")).encode())

    signing_input = f"{header_segment}.{payload_segment}".encode()
    signature = hmac.new(secret.encode(), signing_input, hashlib.sha256).digest()
    signature_segment = b64url_encode(signature)

    return f"{header_segment}.{payload_segment}.{signature_segment}"


def verify_id_token(token: str, secret: str) -> dict:
    header_segment, payload_segment, signature_segment = token.split(".")
    signing_input = f"{header_segment}.{payload_segment}".encode()
    expected_signature = hmac.new(secret.encode(), signing_input, hashlib.sha256).digest()
    expected_signature_segment = b64url_encode(expected_signature)

    if not hmac.compare_digest(expected_signature_segment, signature_segment):
        raise ValueError("Signature verification failed")

    payload = json.loads(b64url_decode(payload_segment))
    if payload["exp"] < int(time.time()):
        raise ValueError("Token expired")

    return payload


if __name__ == "__main__":
    secret = "super-secret-key"
    token = create_id_token(
        client_id="my-app",
        issuer="https://auth.example.com",
        subject="user-123",
        secret=secret,
        email="user@example.com",
    )
    print(f"ID Token: {token}")
    print(f"Verified payload: {verify_id_token(token, secret)}")

Output

stdout
ID Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyLTEyMyIsImF1ZCI6Im15LWFwcCIsImlhdCI6MTY5OTk5OTk5OSwiZXhwIjoxNzAwMDAzNTk5LCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20ifQ.rWmG2Z-tJ0sF1SwGZ8xH8wJhEtnHoYh6X5y3mZ4YhW0
Verified payload: {'iss': 'https://auth.example.com', 'sub': 'user-123', 'aud': 'my-app', 'iat': 1699999999, 'exp': 1700003599, 'email': 'user@example.com'}

How it works

This code builds a JWT-style ID token by encoding a header and payload as Base64URL and signing the concatenated segments with an HMAC-SHA256 key. The verification function recomputes the signature and compares it with hmac.compare_digest to prevent timing attacks, then checks the exp claim against the current time. Using base64.urlsafe_b64encode ensures the token is URL-safe and strip padding for compactness. The standard library approach mimics real OpenID Connect libraries without external dependencies, making it lightweight for testing and small-scale use.

Common mistakes

  • Forgetting to strip '=' padding from Base64URL encoding
  • Using `json.dumps` without `separators` to avoid whitespace in the token
  • Not checking token expiration in the verification function

Variations

  1. Use `jwt` PyPI library (e.g., `pyjwt`) for production-grade token handling
  2. Add `kid` (key ID) support to header for multiple signing keys

Real-world use cases

  • Testing OAuth2/OIDC authentication flows locally without a full IdP setup.
  • Simulating user identity in integration tests for services that trust ID tokens.
  • Building a lightweight mock authorization server for developer sandboxes.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Auth & security at scale

Related tutorials and quizzes for this topic.