CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-authlib

The ultimate Python library in building OAuth and OpenID Connect servers and clients.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

oidc.mddocs/

OpenID Connect Implementation

OpenID Connect implementation built on OAuth 2.0, providing identity layer with ID tokens, UserInfo endpoint, and discovery mechanisms. Supports all OpenID Connect flows: authorization code, implicit, and hybrid, with comprehensive claims validation and JWT-based identity tokens.

Capabilities

ID Token Validation

ID token claims validation for different OpenID Connect flows with support for standard and custom claims.

class IDToken:
    """Base ID token claims validation."""
    
    def __init__(self, claims: dict, header: dict = None, options: dict = None, params: dict = None) -> None:
        """
        Initialize ID token validator.
        
        Args:
            claims: ID token claims dictionary
            header: JWT header dictionary
            options: Validation options
            params: Additional validation parameters
        """

    def validate(self, now: int = None) -> None:
        """
        Validate all ID token claims.
        
        Args:
            now: Current timestamp for time-based validations
        """

    def validate_iss(self) -> None:
        """Validate issuer claim."""

    def validate_sub(self) -> None:
        """Validate subject claim."""

    def validate_aud(self) -> None:
        """Validate audience claim."""

    def validate_exp(self, now: int = None, leeway: int = 0) -> None:
        """
        Validate expiration time claim.
        
        Args:
            now: Current timestamp
            leeway: Acceptable time skew in seconds
        """

    def validate_iat(self, now: int = None, leeway: int = 0) -> None:
        """
        Validate issued at claim.
        
        Args:
            now: Current timestamp
            leeway: Acceptable time skew in seconds
        """

    def validate_auth_time(self, max_age: int = None, now: int = None, leeway: int = 0) -> None:
        """
        Validate authentication time claim.
        
        Args:
            max_age: Maximum authentication age
            now: Current timestamp
            leeway: Acceptable time skew in seconds
        """

    def validate_nonce(self, nonce: str) -> None:
        """
        Validate nonce claim.
        
        Args:
            nonce: Expected nonce value
        """

    def validate_acr(self, values: list) -> None:
        """
        Validate Authentication Context Class Reference.
        
        Args:
            values: List of acceptable ACR values
        """

    def validate_amr(self, values: list) -> None:
        """
        Validate Authentication Methods References.
        
        Args:
            values: List of acceptable AMR values
        """

    def validate_azp(self, client_id: str) -> None:
        """
        Validate authorized party claim.
        
        Args:
            client_id: Expected client ID
        """

class CodeIDToken(IDToken):
    """ID token validation for authorization code flow."""
    
    def validate_at_hash(self, access_token: str, alg: str) -> None:
        """
        Validate access token hash claim.
        
        Args:
            access_token: Access token to verify hash against
            alg: JWT signing algorithm
        """

    def validate_c_hash(self, code: str, alg: str) -> None:
        """
        Validate authorization code hash claim.
        
        Args:
            code: Authorization code to verify hash against
            alg: JWT signing algorithm
        """

class ImplicitIDToken(IDToken):
    """ID token validation for implicit flow."""
    
    def validate_at_hash(self, access_token: str, alg: str) -> None:
        """
        Validate access token hash claim.
        
        Args:
            access_token: Access token to verify hash against
            alg: JWT signing algorithm
        """

class HybridIDToken(IDToken):
    """ID token validation for hybrid flow."""
    
    def validate_at_hash(self, access_token: str, alg: str) -> None:
        """
        Validate access token hash claim.
        
        Args:
            access_token: Access token to verify hash against
            alg: JWT signing algorithm
        """

    def validate_c_hash(self, code: str, alg: str) -> None:
        """
        Validate authorization code hash claim.
        
        Args:
            code: Authorization code to verify hash against
            alg: JWT signing algorithm
        """

UserInfo Endpoint

UserInfo endpoint implementation for serving user claims to relying parties.

class UserInfo:
    """UserInfo claims representation."""
    
    def __init__(self, sub: str, **claims) -> None:
        """
        Initialize UserInfo with subject and claims.
        
        Args:
            sub: Subject identifier (required)
            **claims: Additional user claims
        """

    def __call__(self, claims: list = None) -> dict:
        """
        Get user claims, optionally filtered by requested claims.
        
        Args:
            claims: List of requested claims
            
        Returns:
            Dictionary of user claims
        """

class UserInfoEndpoint:
    """UserInfo endpoint implementation."""
    
    def __init__(self, query_token: callable, query_user: callable) -> None:
        """
        Initialize UserInfo endpoint.
        
        Args:
            query_token: Function to query and validate access token
            query_user: Function to query user by subject identifier
        """

    def create_userinfo_response(self, request: OAuth2Request) -> tuple:
        """
        Create UserInfo endpoint response.
        
        Args:
            request: OAuth2Request object
            
        Returns:
            Tuple of (status_code, body, headers)
        """

    def validate_userinfo_request(self, request: OAuth2Request) -> dict:
        """
        Validate UserInfo request and return token.
        
        Args:
            request: OAuth2Request object
            
        Returns:
            Token dictionary
        """

    def generate_userinfo(self, user: object, scope: str) -> dict:
        """
        Generate UserInfo response for user and scope.
        
        Args:
            user: User object
            scope: Access token scope
            
        Returns:
            UserInfo claims dictionary
        """

OpenID Connect Grant Types

Enhanced OAuth 2.0 grants with OpenID Connect ID token support.

class OpenIDCode:
    """OpenID Connect authorization code grant enhancement."""
    
    def create_id_token(self, token: dict, request: OAuth2Request, nonce: str = None) -> str:
        """
        Create ID token for authorization code flow.
        
        Args:
            token: Access token dictionary
            request: OAuth2Request object
            nonce: Nonce from authorization request
            
        Returns:
            ID token JWT string
        """

    def get_jwt_config(self, grant: object) -> dict:
        """
        Get JWT configuration for ID token.
        
        Args:
            grant: Grant object
            
        Returns:
            JWT configuration dictionary
        """

    def generate_user_info(self, user: object, scope: str) -> dict:
        """
        Generate user info claims for ID token.
        
        Args:
            user: User object
            scope: Token scope
            
        Returns:
            User info claims dictionary
        """

class OpenIDToken:
    """OpenID Connect token grant (refresh token with ID token)."""
    
    def create_id_token(self, token: dict, request: OAuth2Request, nonce: str = None) -> str:
        """
        Create ID token for token refresh.
        
        Args:
            token: Access token dictionary
            request: OAuth2Request object
            nonce: Nonce from original authorization
            
        Returns:
            ID token JWT string
        """

class OpenIDImplicitGrant:
    """OpenID Connect implicit grant implementation."""
    
    RESPONSE_TYPES: list = ['id_token', 'id_token token']
    
    def create_id_token(self, grant_user: callable, request: OAuth2Request) -> str:
        """
        Create ID token for implicit flow.
        
        Args:
            grant_user: Function to grant user
            request: OAuth2Request object
            
        Returns:
            ID token JWT string
        """

    def exists_nonce(self, nonce: str, request: OAuth2Request) -> bool:
        """
        Check if nonce exists and is valid.
        
        Args:
            nonce: Nonce value
            request: OAuth2Request object
            
        Returns:
            True if nonce is valid
        """

class OpenIDHybridGrant:
    """OpenID Connect hybrid grant implementation."""
    
    RESPONSE_TYPES: list = ['code id_token', 'code token', 'code id_token token']
    
    def create_id_token(self, token: dict, request: OAuth2Request, nonce: str = None, code: str = None) -> str:
        """
        Create ID token for hybrid flow.
        
        Args:
            token: Access token dictionary (if issued)
            request: OAuth2Request object
            nonce: Nonce from authorization request
            code: Authorization code (if issued)
            
        Returns:
            ID token JWT string
        """

OpenID Connect Discovery

Provider metadata and discovery implementation following OpenID Connect Discovery 1.0.

class OpenIDProviderMetadata:
    """OpenID Connect provider metadata."""
    
    def __init__(self, issuer: str, **metadata) -> None:
        """
        Initialize provider metadata.
        
        Args:
            issuer: Provider issuer identifier
            **metadata: Additional metadata parameters
        """

    def validate(self) -> None:
        """Validate provider metadata for required fields."""

    def get_jwks_uri(self) -> str:
        """Get JSON Web Key Set URI."""

    def get_supported_scopes(self) -> list:
        """Get list of supported scopes."""

    def get_supported_response_types(self) -> list:
        """Get list of supported response types."""

    def get_supported_grant_types(self) -> list:
        """Get list of supported grant types."""

    def get_supported_id_token_signing_alg_values(self) -> list:
        """Get list of supported ID token signing algorithms."""

    def get_supported_claims(self) -> list:
        """Get list of supported claims."""

def get_well_known_url(issuer: str, external: bool = False) -> str:
    """
    Generate OpenID Connect well-known configuration URL.
    
    Args:
        issuer: Provider issuer identifier
        external: Whether to use external URL format
        
    Returns:
        Well-known configuration URL
    """

Client Registration

Dynamic client registration following OpenID Connect Registration 1.0.

class ClientMetadataClaims:
    """Client metadata claims validation."""
    
    def __init__(self, claims: dict, **params) -> None:
        """
        Initialize client metadata validator.
        
        Args:
            claims: Client metadata claims
            **params: Additional validation parameters
        """

    def validate(self) -> None:
        """Validate all client metadata claims."""

    def validate_redirect_uris(self) -> None:
        """Validate redirect URIs."""

    def validate_response_types(self) -> None:
        """Validate response types."""

    def validate_grant_types(self) -> None:
        """Validate grant types."""

    def validate_application_type(self) -> None:
        """Validate application type (web or native)."""

    def validate_client_name(self) -> None:
        """Validate client name."""

    def validate_client_uri(self) -> None:
        """Validate client URI."""

    def validate_logo_uri(self) -> None:
        """Validate logo URI."""

    def validate_scope(self) -> None:
        """Validate requested scopes."""

    def validate_contacts(self) -> None:
        """Validate contact information."""

    def validate_tos_uri(self) -> None:
        """Validate terms of service URI."""

    def validate_policy_uri(self) -> None:
        """Validate privacy policy URI."""

    def validate_jwks_uri(self) -> None:
        """Validate JWKS URI."""

    def validate_jwks(self) -> None:
        """Validate embedded JWKS."""

    def validate_software_id(self) -> None:
        """Validate software ID."""

    def validate_software_version(self) -> None:
        """Validate software version."""

Authorization Code Mixin

Enhanced authorization code mixin with OpenID Connect support.

class AuthorizationCodeMixin:
    """Mixin for OIDC authorization code model."""
    
    code: str  # Authorization code
    client_id: str  # Client identifier
    redirect_uri: str  # Redirect URI
    scope: str  # Authorized scope
    user_id: str  # User identifier
    code_challenge: str  # PKCE code challenge
    code_challenge_method: str  # PKCE challenge method
    nonce: str  # OpenID Connect nonce
    
    def get_nonce(self) -> str:
        """
        Get OpenID Connect nonce.
        
        Returns:
            Nonce value
        """

    def get_auth_time(self) -> int:
        """
        Get authentication time.
        
        Returns:
            Authentication timestamp
        """

Utility Functions

Helper functions for OpenID Connect flows and claim handling.

def get_claim_cls_by_response_type(response_type: str) -> type:
    """
    Get appropriate ID token claims class by response type.
    
    Args:
        response_type: OAuth 2.0/OIDC response type
        
    Returns:
        ID token claims validation class
    """

Usage Examples

ID Token Validation

from authlib.oidc.core import IDToken, CodeIDToken
from authlib.jose import JsonWebToken

# Decode and validate ID token
id_token_string = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...'
jwt = JsonWebToken(['RS256'])

# Decode JWT
claims = jwt.decode(id_token_string, public_key)

# Validate ID token claims for authorization code flow
id_token = CodeIDToken(claims, header={'alg': 'RS256'})
id_token.validate()

# Validate access token hash (for code flow)
id_token.validate_at_hash('access-token-value', 'RS256')

# Validate with specific parameters
id_token = IDToken(claims, options={
    'iss': {'essential': True, 'value': 'https://provider.com'},
    'aud': {'essential': True, 'value': 'client-id'}
})
id_token.validate()

UserInfo Endpoint

from authlib.oidc.core import UserInfo, UserInfoEndpoint
from authlib.oauth2 import OAuth2Request

# Create UserInfo representation
user_info = UserInfo(
    sub='user123',
    name='John Doe',
    email='john@example.com',
    email_verified=True,
    picture='https://example.com/photo.jpg'
)

# Get filtered claims
profile_claims = user_info(['name', 'picture'])
# Returns: {'sub': 'user123', 'name': 'John Doe', 'picture': '...'}

# Server-side UserInfo endpoint
def query_token(access_token):
    # Validate and return token info
    return get_token_by_value(access_token)

def query_user(sub):
    # Return user by subject
    return get_user_by_id(sub)

userinfo_endpoint = UserInfoEndpoint(
    query_token=query_token,
    query_user=query_user
)

# Handle UserInfo request
request = OAuth2Request('GET', '/userinfo', headers={'Authorization': 'Bearer token'})
status_code, body, headers = userinfo_endpoint.create_userinfo_response(request)

OpenID Connect Authorization Server

from authlib.oauth2 import AuthorizationServer
from authlib.oauth2.rfc6749.grants import AuthorizationCodeGrant
from authlib.oidc.core.grants import OpenIDCode
from authlib.oidc.core import UserInfo

# Enhance authorization code grant with OpenID Connect
class AuthorizationCodeGrantWithOpenID(AuthorizationCodeGrant):
    def __init__(self, request, server):
        super().__init__(request, server)
        # Add OpenID Connect support
        self.openid_code = OpenIDCode()

    def create_token_response(self):
        token = self.generate_token()
        
        # Add ID token for OpenID Connect requests
        if 'openid' in self.request.scope:
            id_token = self.openid_code.create_id_token(
                token=token,
                request=self.request,
                nonce=self.request.data.get('nonce')
            )
            token['id_token'] = id_token
        
        return token

# Register enhanced grant
authorization_server = AuthorizationServer(query_client, save_token)
authorization_server.register_grant(AuthorizationCodeGrantWithOpenID)

# Add UserInfo endpoint
userinfo_endpoint = UserInfoEndpoint(query_token, query_user)
authorization_server.register_endpoint(userinfo_endpoint)

Discovery Endpoint

from authlib.oidc.discovery import OpenIDProviderMetadata, get_well_known_url

# Create provider metadata
metadata = OpenIDProviderMetadata(
    issuer='https://provider.com',
    authorization_endpoint='https://provider.com/authorize',
    token_endpoint='https://provider.com/token',
    userinfo_endpoint='https://provider.com/userinfo',
    jwks_uri='https://provider.com/.well-known/jwks.json',
    scopes_supported=['openid', 'profile', 'email'],
    response_types_supported=['code', 'token', 'id_token', 'code token', 'code id_token', 'id_token token', 'code id_token token'],
    subject_types_supported=['public'],
    id_token_signing_alg_values_supported=['RS256', 'HS256'],
    claims_supported=['sub', 'iss', 'aud', 'exp', 'iat', 'name', 'email', 'email_verified']
)

# Validate metadata
metadata.validate()

# Generate well-known URL
well_known_url = get_well_known_url('https://provider.com')
# Returns: https://provider.com/.well-known/openid_configuration

# Serve discovery endpoint
@app.route('/.well-known/openid_configuration')
def openid_configuration():
    return jsonify(metadata.__dict__)

Client Integration

from authlib.integrations.requests_client import OAuth2Session
from authlib.oidc.core import IDToken
from authlib.jose import JsonWebToken

# OpenID Connect client flow
client = OAuth2Session(
    client_id='client-id',
    client_secret='client-secret',
    scope='openid profile email'
)

# Authorization with nonce
import secrets
nonce = secrets.token_urlsafe(32)

auth_url, state = client.create_authorization_url(
    'https://provider.com/authorize',
    nonce=nonce
)

# Exchange code for tokens (including ID token)
token = client.fetch_token(
    'https://provider.com/token',
    authorization_response='https://callback.com?code=...'
)

# Validate ID token
if 'id_token' in token:
    jwt = JsonWebToken(['RS256'])
    # Get provider's public key from JWKS endpoint
    public_key = get_provider_public_key()
    
    # Decode and validate ID token
    claims = jwt.decode(token['id_token'], public_key)
    id_token = IDToken(claims)
    id_token.validate()
    id_token.validate_nonce(nonce)
    
    print(f"User ID: {claims['sub']}")
    print(f"User name: {claims.get('name')}")

# Get additional user info
userinfo_response = client.get('https://provider.com/userinfo')
user_claims = userinfo_response.json()

Install with Tessl CLI

npx tessl i tessl/pypi-authlib

docs

common-utilities.md

django-integration.md

flask-integration.md

http-clients.md

index.md

jose.md

oauth1.md

oauth2.md

oidc.md

tile.json