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.
Python code
75 linesimport 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
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
- Use `jwt` PyPI library (e.g., `pyjwt`) for production-grade token handling
- 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
More from Auth & security at scale
- ACME LetsEncrypt Mock Challenge Server in Python medium
- AES GCM encryption and decryption in Python medium
- Build a Mock OIDC Userinfo Endpoint in Python with Flask easy
- ChaCha20-Poly1305 mock in Python medium
- ECDH key agreement in Python with cryptography medium
- Enforce TLS 1.2 Minimum in Python easy
Keep learning
Related tutorials and quizzes for this topic.