Skip to content

API Reference

This page documents the package public API and optional integration modules.

Core Public API

Available in the base install:

AuthConfig(issuer: str, audience: str | Sequence[str], jwks_url: str, allowed_algs: Sequence[str] = ('RS256',), leeway_s: int = 0, jwks_timeout_s: float = 3.0, jwks_cache_ttl_s: float = 300.0, jwks_max_cached_keys: int = 16, enforce_minimum_key_length: bool = True, required_scopes: Sequence[str] = (), required_permissions: Sequence[str] = (), scope_claim: str = 'scope', permissions_claim: str = 'permissions') dataclass

Immutable configuration for JWT verification.

This dataclass holds all settings required by JWTVerifier to validate JWTs against an OIDC provider. The configuration is frozen (immutable) and uses slots for memory efficiency.

All string inputs are stripped of leading/trailing whitespace during validation. Sequences are normalized to tuples.

Attributes:

Name Type Description
issuer str

The expected iss claim value. Must match the token issuer exactly. Typically the OIDC provider URL (e.g., https://example.auth0.com/).

audience str | Sequence[str]

One or more expected aud claim values. The token must contain at least one matching audience. Accepts a single string or a sequence of strings.

jwks_url str

The URL to fetch the JSON Web Key Set from. This URL is used for all key lookups; the verifier never derives JWKS URLs from token headers.

allowed_algs Sequence[str]

Permitted signing algorithms. Defaults to ("RS256",). The none algorithm is always rejected regardless of this setting.

leeway_s int

Clock skew tolerance in seconds for exp and nbf claim validation. Defaults to 0.

jwks_timeout_s float

HTTP timeout in seconds for JWKS fetches. Supports fractional seconds. Defaults to 3.0.

jwks_cache_ttl_s float

Time-to-live in seconds for cached JWKS data. Supports fractional seconds and must be in the range (0, 86400]. Defaults to 300.0.

jwks_max_cached_keys int

Maximum number of signing keys to cache. Must be in the range (0, 1024]. Defaults to 16.

enforce_minimum_key_length bool

Whether to reject JWTs signed with cryptographic keys below PyJWT minimum recommendations. Defaults to True.

required_scopes Sequence[str]

Scopes that must be present in the token for authorization to succeed. Checked against the scope_claim. Defaults to an empty tuple (no scope requirements).

required_permissions Sequence[str]

Permissions that must be present in the token. Checked against the permissions_claim. Defaults to an empty tuple.

scope_claim str

The claim name containing OAuth 2.0 scopes. Defaults to "scope".

permissions_claim str

The claim name containing permissions (commonly used by Auth0). Defaults to "permissions".

Raises:

Type Description
ValueError

If any validation constraint is violated during construction. Specific conditions include: - Empty or whitespace-only issuer, jwks_url, or audience. - Empty or whitespace-only allowed_algs, or inclusion of the none algorithm. - Negative leeway_s. - Non-positive jwks_timeout_s. - jwks_cache_ttl_s outside (0, 86400]. - jwks_max_cached_keys outside (0, 1024]. - Empty or whitespace-only scope_claim or permissions_claim.

Examples:

Minimal configuration for Auth0:

>>> config = AuthConfig(
...     issuer="https://example.auth0.com/",
...     audience="https://api.example.com",
...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
... )
>>> config.audiences
('https://api.example.com',)
>>> config.allowed_algorithms
('RS256',)

Configuration with multiple audiences and scope requirements:

>>> config = AuthConfig(
...     issuer="https://example.auth0.com/",
...     audience=[
...         "https://api.example.com",
...         "https://api2.example.com",
...     ],
...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
...     allowed_algs=["RS256", "RS384"],
...     required_scopes=["read:users", "write:users"],
... )
>>> config.audiences
('https://api.example.com', 'https://api2.example.com')
>>> config.required_scope_set
{'read:users', 'write:users'}

Invalid configuration raises ValueError:

>>> AuthConfig(
...     issuer="",
...     audience="api",
...     jwks_url="https://example.com/.well-known/jwks.json",
... )
Traceback (most recent call last):
    ...
ValueError: issuer must be non-empty

audiences: tuple[str, ...] property

Return the configured audiences as a tuple.

This property provides consistent tuple access regardless of whether the audience attribute was initialized with a single string or a sequence.

Returns:

Type Description
tuple[str, ...]

A tuple of audience strings.

Examples:

>>> config = AuthConfig(
...     issuer="https://example.auth0.com/",
...     audience="https://api.example.com",
...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
... )
>>> config.audiences
('https://api.example.com',)

allowed_algorithms: tuple[str, ...] property

Return the allowed algorithms as a tuple.

This property provides consistent tuple access regardless of whether the allowed_algs attribute was initialized with a single string or a sequence.

Returns:

Type Description
tuple[str, ...]

A tuple of algorithm name strings.

Examples:

>>> config = AuthConfig(
...     issuer="https://example.auth0.com/",
...     audience="https://api.example.com",
...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
...     allowed_algs=["RS256", "ES256"],
... )
>>> config.allowed_algorithms
('RS256', 'ES256')

required_scope_set: set[str] property

Return the required scopes as a set for efficient membership testing.

Empty strings in the required_scopes sequence are filtered out.

Returns:

Type Description
set[str]

A set of non-empty scope strings.

Examples:

>>> config = AuthConfig(
...     issuer="https://example.auth0.com/",
...     audience="https://api.example.com",
...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
...     required_scopes=["read:users", "write:users"],
... )
>>> config.required_scope_set == {"read:users", "write:users"}
True

required_permission_set: set[str] property

Return the required permissions as a set for efficient membership testing.

Empty strings in the required_permissions sequence are filtered out.

Returns:

Type Description
set[str]

A set of non-empty permission strings.

Examples:

>>> config = AuthConfig(
...     issuer="https://example.auth0.com/",
...     audience="https://api.example.com",
...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
...     required_permissions=["admin", "editor"],
... )
>>> config.required_permission_set == {"admin", "editor"}
True

__post_init__() -> None

Validate and normalize configuration values after initialization.

This method runs automatically after dataclass initialization. It strips whitespace from string values, normalizes sequences to tuples, and validates all constraints.

Raises:

Type Description
ValueError

If any validation constraint is violated.

Source code in oidc_jwt_verifier/config.py
def __post_init__(self) -> None:
    """Validate and normalize configuration values after initialization.

    This method runs automatically after dataclass initialization. It
    strips whitespace from string values, normalizes sequences to tuples,
    and validates all constraints.

    Raises:
        ValueError: If any validation constraint is violated.
    """
    issuer = self.issuer.strip()
    if not issuer:
        raise ValueError("issuer must be non-empty")
    object.__setattr__(self, "issuer", issuer)

    jwks_url = self.jwks_url.strip()
    if not jwks_url:
        raise ValueError("jwks_url must be non-empty")
    object.__setattr__(self, "jwks_url", jwks_url)

    audiences = tuple(
        a.strip() for a in _normalize_str_sequence(self.audience)
    )
    if not audiences or any(not a for a in audiences):
        raise ValueError("audience must be non-empty")
    object.__setattr__(self, "audience", audiences)

    allowed_algs = tuple(
        a.strip() for a in _normalize_str_sequence(self.allowed_algs)
    )
    if not allowed_algs or any(not a for a in allowed_algs):
        raise ValueError("allowed_algs must be non-empty")
    if any(a.lower() == "none" for a in allowed_algs):
        raise ValueError("allowed_algs must not include 'none'")
    object.__setattr__(self, "allowed_algs", allowed_algs)

    if self.leeway_s < 0:
        raise ValueError("leeway_s must be >= 0")

    if self.jwks_timeout_s <= 0:
        raise ValueError("jwks_timeout_s must be > 0")
    if not 0 < self.jwks_cache_ttl_s <= 24 * 60 * 60:
        raise ValueError("jwks_cache_ttl_s must be in (0, 86400]")
    if not 0 < self.jwks_max_cached_keys <= 1024:
        raise ValueError("jwks_max_cached_keys must be in (0, 1024]")

    object.__setattr__(
        self,
        "required_scopes",
        tuple(
            s.strip() for s in _normalize_str_sequence(self.required_scopes)
        ),
    )
    object.__setattr__(
        self,
        "required_permissions",
        tuple(
            p.strip()
            for p in _normalize_str_sequence(self.required_permissions)
        ),
    )

    scope_claim = self.scope_claim.strip()
    if not scope_claim:
        raise ValueError("scope_claim must be non-empty")
    object.__setattr__(self, "scope_claim", scope_claim)

    permissions_claim = self.permissions_claim.strip()
    if not permissions_claim:
        raise ValueError("permissions_claim must be non-empty")
    object.__setattr__(self, "permissions_claim", permissions_claim)

AuthError(*, code: str, message: str, status_code: int, required_scopes: Iterable[str] = (), required_permissions: Iterable[str] = ())

Bases: Exception

Exception raised on authentication or authorization failure.

This exception provides structured error information including a stable error code for programmatic handling, an HTTP status code (401 for authentication failures, 403 for authorization failures), and a method to generate RFC 6750-compliant WWW-Authenticate header values.

The exception message is accessible via the standard str() conversion or the message attribute.

Attributes:

Name Type Description
code

A stable string identifier for the error type. Common values include "invalid_token", "token_expired", "insufficient_scope", and "missing_token". Suitable for programmatic error handling and logging.

message

A human-readable description of the error. This is also set as the exception message.

status_code

The HTTP status code to return. Must be 401 (Unauthorized) for authentication errors or 403 (Forbidden) for authorization errors.

required_scopes

A tuple of scope strings that were required but missing from the token. Populated for insufficient_scope errors; empty for other error types.

required_permissions

A tuple of permission strings that were required but missing from the token. Populated for insufficient_permissions errors; empty for other error types.

Raises:

Type Description
ValueError

If status_code is not 401 or 403.

Examples:

Creating an authentication error (401):

>>> error = AuthError(
...     code="token_expired",
...     message="Token is expired",
...     status_code=401,
... )
>>> str(error)
'Token is expired'
>>> error.code
'token_expired'
>>> error.status_code
401

Creating an authorization error (403) with scope requirements:

>>> error = AuthError(
...     code="insufficient_scope",
...     message="Insufficient scope",
...     status_code=403,
...     required_scopes=["read:users", "write:users"],
... )
>>> error.required_scopes
('read:users', 'write:users')

Generating a WWW-Authenticate header:

>>> error = AuthError(
...     code="invalid_token",
...     message="Malformed token",
...     status_code=401,
... )
>>> error.www_authenticate_header(realm="api")
'Bearer realm="api", error="invalid_token", error_description="Malformed token"'

Invalid status code raises ValueError:

>>> AuthError(code="error", message="msg", status_code=500)
Traceback (most recent call last):
    ...
ValueError: status_code must be 401 or 403

Initialize an authentication or authorization error.

Parameters:

Name Type Description Default
code str

A stable string identifier for the error type.

required
message str

A human-readable error description.

required
status_code int

The HTTP status code (must be 401 or 403).

required
required_scopes Iterable[str]

Scopes that were required but missing. Defaults to an empty tuple.

()
required_permissions Iterable[str]

Permissions that were required but missing. Defaults to an empty tuple.

()

Raises:

Type Description
ValueError

If status_code is not 401 or 403.

Source code in oidc_jwt_verifier/errors.py
def __init__(
    self,
    *,
    code: str,
    message: str,
    status_code: int,
    required_scopes: Iterable[str] = (),
    required_permissions: Iterable[str] = (),
) -> None:
    """Initialize an authentication or authorization error.

    Args:
        code: A stable string identifier for the error type.
        message: A human-readable error description.
        status_code: The HTTP status code (must be 401 or 403).
        required_scopes: Scopes that were required but missing.
            Defaults to an empty tuple.
        required_permissions: Permissions that were required but missing.
            Defaults to an empty tuple.

    Raises:
        ValueError: If ``status_code`` is not 401 or 403.
    """
    if status_code not in (401, 403):
        raise ValueError("status_code must be 401 or 403")
    super().__init__(message)
    self.code = code
    self.message = message
    self.status_code = status_code
    self.required_scopes = tuple(required_scopes)
    self.required_permissions = tuple(required_permissions)

www_authenticate_header(*, realm: str | None = None) -> str

Generate an RFC 6750-compliant WWW-Authenticate header value.

Constructs a Bearer authentication challenge suitable for use as the value of an HTTP WWW-Authenticate header. The challenge includes the error type (mapped to RFC 6750 error codes) and a description.

RFC 6750 defines two relevant error codes: - invalid_token: Used for 401 errors (authentication failures). - insufficient_scope: Used for 403 errors (authorization failures).

If required_scopes is non-empty, a scope parameter is included listing the missing scopes.

Parameters:

Name Type Description Default
realm str | None

Optional protection space identifier. If provided, it appears first in the challenge parameters. Common values include the API name or domain.

None

Returns:

Type Description
str

A string suitable for use as the WWW-Authenticate header value.

str

The format is Bearer param1="value1", param2="value2", ....

Examples:

Basic authentication error:

>>> error = AuthError(
...     code="invalid_token",
...     message="Token is expired",
...     status_code=401,
... )
>>> error.www_authenticate_header()
'Bearer error="invalid_token", error_description="Token is expired"'

With realm:

>>> error.www_authenticate_header(realm="my-api")
'Bearer realm="my-api", error="invalid_token", error_description="Token is expired"'

Authorization error with required scopes:

>>> error = AuthError(
...     code="insufficient_scope",
...     message="Insufficient scope",
...     status_code=403,
...     required_scopes=["read:users"],
... )
>>> header = error.www_authenticate_header()
>>> "insufficient_scope" in header
True
>>> "read:users" in header
True
Source code in oidc_jwt_verifier/errors.py
def www_authenticate_header(self, *, realm: str | None = None) -> str:
    """Generate an RFC 6750-compliant WWW-Authenticate header value.

    Constructs a Bearer authentication challenge suitable for use as the
    value of an HTTP WWW-Authenticate header. The challenge includes
    the error type (mapped to RFC 6750 error codes) and a description.

    RFC 6750 defines two relevant error codes:
    - ``invalid_token``: Used for 401 errors (authentication failures).
    - ``insufficient_scope``: Used for 403 errors (authorization failures).

    If ``required_scopes`` is non-empty, a ``scope`` parameter is
    included listing the missing scopes.

    Args:
        realm: Optional protection space identifier. If provided, it
            appears first in the challenge parameters. Common values
            include the API name or domain.

    Returns:
        A string suitable for use as the WWW-Authenticate header value.
        The format is ``Bearer param1="value1", param2="value2", ...``.

    Examples:
        Basic authentication error:

        >>> error = AuthError(
        ...     code="invalid_token",
        ...     message="Token is expired",
        ...     status_code=401,
        ... )
        >>> error.www_authenticate_header()
        'Bearer error="invalid_token", error_description="Token is expired"'

        With realm:

        >>> error.www_authenticate_header(realm="my-api")
        'Bearer realm="my-api", error="invalid_token", error_description="Token is expired"'

        Authorization error with required scopes:

        >>> error = AuthError(
        ...     code="insufficient_scope",
        ...     message="Insufficient scope",
        ...     status_code=403,
        ...     required_scopes=["read:users"],
        ... )
        >>> header = error.www_authenticate_header()
        >>> "insufficient_scope" in header
        True
        >>> "read:users" in header
        True
    """
    params: list[str] = []
    if realm is not None:
        params.append(f"realm={_quote_rfc6750_value(realm)}")

    if self.status_code == 403:
        rfc6750_error = "insufficient_scope"
    else:
        rfc6750_error = "invalid_token"

    params.append(f"error={_quote_rfc6750_value(rfc6750_error)}")
    params.append(f"error_description={_quote_rfc6750_value(self.message)}")

    if self.required_scopes:
        scope_str = " ".join(self.required_scopes)
        params.append(f"scope={_quote_rfc6750_value(scope_str)}")

    if self.required_permissions:
        permissions_str = " ".join(self.required_permissions)
        params.append(
            f"permissions={_quote_rfc6750_value(permissions_str)}"
        )

    return "Bearer " + ", ".join(params)

JWTVerifier(config: AuthConfig)

Stateful JWT verifier for OIDC access tokens.

This class performs complete JWT verification including:

  1. Header validation: Rejects tokens with dangerous headers (jku, x5u, crit) and ensures the algorithm is in the allowlist.
  2. Key retrieval: Fetches the signing key from the JWKS using the token's kid header.
  3. Signature verification: Validates the cryptographic signature.
  4. Claim validation: Checks iss, aud, exp, and nbf claims against configuration.
  5. Authorization enforcement: Verifies required scopes and permissions are present (returns 403 on failure).

The verifier maintains a cached JWKS client for efficient key lookups across multiple token verifications.

Attributes:

Name Type Description
_config

The authentication configuration.

_jwks

The JWKS client for signing key retrieval.

_decoder

A configured jwt.PyJWT instance used for token decoding.

Examples:

Basic token verification:

>>> from oidc_jwt_verifier import AuthConfig, AuthError, JWTVerifier
>>> config = AuthConfig(
...     issuer="https://example.auth0.com/",
...     audience="https://api.example.com",
...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
... )
>>> verifier = JWTVerifier(config)
>>> claims = verifier.verify_access_token(token)
>>> claims["sub"]
'auth0|123456789'

Handling verification errors:

>>> try:
...     claims = verifier.verify_access_token(expired_token)
... except AuthError as e:
...     print(f"Error: {e.code}, Status: {e.status_code}")
...     print(e.www_authenticate_header())
Error: token_expired, Status: 401
Bearer error="invalid_token", error_description="Token is expired"

Verifying tokens with scope requirements:

>>> config = AuthConfig(
...     issuer="https://example.auth0.com/",
...     audience="https://api.example.com",
...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
...     required_scopes=["read:users"],
... )
>>> verifier = JWTVerifier(config)
>>> # Token without required scopes raises AuthError with 403
>>> claims = verifier.verify_access_token(
...     token_without_scopes
... )
Traceback (most recent call last):
    ...
AuthError: Insufficient scope

Initialize a JWT verifier with the given configuration.

Creates a JWKS client configured with the caching parameters from the provided configuration.

Parameters:

Name Type Description Default
config AuthConfig

The authentication configuration specifying the issuer, audience, JWKS URL, allowed algorithms, and authorization requirements.

required

Examples:

>>> from oidc_jwt_verifier import AuthConfig
>>> config = AuthConfig(
...     issuer="https://example.auth0.com/",
...     audience="https://api.example.com",
...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
... )
>>> verifier = JWTVerifier(config)
Source code in oidc_jwt_verifier/verifier.py
def __init__(self, config: AuthConfig) -> None:
    """Initialize a JWT verifier with the given configuration.

    Creates a JWKS client configured with the caching parameters
    from the provided configuration.

    Args:
        config: The authentication configuration specifying the
            issuer, audience, JWKS URL, allowed algorithms, and
            authorization requirements.

    Examples:
        >>> from oidc_jwt_verifier import AuthConfig
        >>> config = AuthConfig(
        ...     issuer="https://example.auth0.com/",
        ...     audience="https://api.example.com",
        ...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
        ... )
        >>> verifier = JWTVerifier(config)  # doctest: +SKIP
    """
    self._config = config
    self._jwks = JWKSClient.from_config(config)
    self._decoder = jwt.PyJWT(
        options={
            "enforce_minimum_key_length": config.enforce_minimum_key_length
        }
    )

verify_access_token(token: str) -> dict[str, Any]

Verify an access token and return its claims.

Performs the complete verification chain:

  1. Validates the token is non-empty.
  2. Parses and validates the token header (rejects jku, x5u, crit; validates alg and kid).
  3. Fetches the signing key from the JWKS.
  4. Decodes and verifies the token signature.
  5. Validates standard claims (iss, aud, exp, nbf).
  6. Enforces required scopes and permissions.

The method supports Auth0-style multi-audience tokens where the aud claim is an array. Verification succeeds if any configured audience matches any audience in the token.

Parameters:

Name Type Description Default
token str

The encoded JWT access token string. Leading and trailing whitespace is stripped.

required

Returns:

Type Description
dict[str, Any]

The decoded token payload as a dictionary. Contains all

dict[str, Any]

claims from the token including registered claims (iss,

dict[str, Any]

sub, aud, exp, etc.) and any custom claims.

Raises:

Type Description
AuthError

On any verification failure. The error's status_code indicates the appropriate HTTP response: - 401 for authentication failures (missing token, malformed token, invalid signature, expired token, wrong issuer/audience). - 403 for authorization failures (insufficient scopes or permissions).

Specific error codes include: - "missing_token": Empty or whitespace-only token. - "malformed_token": Unparseable token or missing alg header. - "forbidden_header": Token contains jku, x5u, or crit headers. - "disallowed_alg": Algorithm not in allowlist or is none. - "missing_kid": Token lacks kid header. - "token_expired": Token exp is in the past. - "token_not_yet_valid": Token nbf is in the future. - "invalid_issuer": iss claim mismatch. - "invalid_audience": aud claim mismatch. - "insufficient_scope": Missing required scopes. - "insufficient_permissions": Missing required permissions.

Examples:

Successful verification:

>>> claims = verifier.verify_access_token(
...     valid_token
... )
>>> claims["sub"]
'auth0|123456789'
>>> claims["aud"]
'https://api.example.com'

Missing token:

>>> verifier.verify_access_token("")
Traceback (most recent call last):
    ...
AuthError: Missing access token

Expired token:

>>> verifier.verify_access_token(expired_token)
Traceback (most recent call last):
    ...
AuthError: Token is expired
Source code in oidc_jwt_verifier/verifier.py
def verify_access_token(self, token: str) -> dict[str, Any]:
    """Verify an access token and return its claims.

    Performs the complete verification chain:

    1. Validates the token is non-empty.
    2. Parses and validates the token header (rejects ``jku``, ``x5u``,
       ``crit``; validates ``alg`` and ``kid``).
    3. Fetches the signing key from the JWKS.
    4. Decodes and verifies the token signature.
    5. Validates standard claims (``iss``, ``aud``, ``exp``, ``nbf``).
    6. Enforces required scopes and permissions.

    The method supports Auth0-style multi-audience tokens where the
    ``aud`` claim is an array. Verification succeeds if any configured
    audience matches any audience in the token.

    Args:
        token: The encoded JWT access token string. Leading and
            trailing whitespace is stripped.

    Returns:
        The decoded token payload as a dictionary. Contains all
        claims from the token including registered claims (``iss``,
        ``sub``, ``aud``, ``exp``, etc.) and any custom claims.

    Raises:
        AuthError: On any verification failure. The error's
            ``status_code`` indicates the appropriate HTTP response:
            - 401 for authentication failures (missing token,
              malformed token, invalid signature, expired token,
              wrong issuer/audience).
            - 403 for authorization failures (insufficient scopes
              or permissions).

            Specific error codes include:
            - ``"missing_token"``: Empty or whitespace-only token.
            - ``"malformed_token"``: Unparseable token or missing
              ``alg`` header.
            - ``"forbidden_header"``: Token contains ``jku``,
              ``x5u``, or ``crit`` headers.
            - ``"disallowed_alg"``: Algorithm not in allowlist or
              is ``none``.
            - ``"missing_kid"``: Token lacks ``kid`` header.
            - ``"token_expired"``: Token ``exp`` is in the past.
            - ``"token_not_yet_valid"``: Token ``nbf`` is in the
              future.
            - ``"invalid_issuer"``: ``iss`` claim mismatch.
            - ``"invalid_audience"``: ``aud`` claim mismatch.
            - ``"insufficient_scope"``: Missing required scopes.
            - ``"insufficient_permissions"``: Missing required
              permissions.

    Examples:
        Successful verification:

        >>> claims = verifier.verify_access_token(
        ...     valid_token
        ... )  # doctest: +SKIP
        >>> claims["sub"]  # doctest: +SKIP
        'auth0|123456789'
        >>> claims["aud"]  # doctest: +SKIP
        'https://api.example.com'

        Missing token:

        >>> verifier.verify_access_token("")  # doctest: +SKIP
        Traceback (most recent call last):
            ...
        AuthError: Missing access token

        Expired token:

        >>> verifier.verify_access_token(expired_token)  # doctest: +SKIP
        Traceback (most recent call last):
            ...
        AuthError: Token is expired
    """
    token = token.strip()
    if not token:
        raise AuthError(
            code="missing_token",
            message="Missing access token",
            status_code=401,
        )

    _, alg = parse_and_validate_header(
        token, allowed_algorithms=self._config.allowed_algorithms
    )
    signing_key = self._jwks.get_signing_key_from_jwt(token)

    payload = decode_and_validate_payload(
        decoder=self._decoder,
        token=token,
        signing_key=signing_key,
        algorithm=alg,
        config=self._config,
    )
    enforce_authorization_claims(payload, config=self._config)
    return payload

Optional Async API

Requires pip install "oidc-jwt-verifier[async]".

  • oidc_jwt_verifier.async_jwks.AsyncJWKSClient
  • oidc_jwt_verifier.async_verifier.AsyncJWTVerifier

AsyncJWKSClient(_config: AuthConfig, _client: httpx.AsyncClient, _owns_client: bool, _max_fetch_attempts: int = 2, _jwk_set_cache: PyJWKSet | None = None, _jwk_set_expiry_monotonic: float = 0.0, _key_cache: OrderedDict[str, PyJWK] = OrderedDict(), _lock: asyncio.Lock = asyncio.Lock()) dataclass

Asynchronous JWKS client with TTL and key caching.

The client supports:

  • Async JWKS fetches via httpx.AsyncClient.
  • JWKS document caching with jwks_cache_ttl_s.
  • Key-object caching with jwks_max_cached_keys.
  • Error mapping to stable AuthError codes.

Parameters:

Name Type Description Default
_config AuthConfig

Auth configuration.

required
_client AsyncClient

Async HTTP client used for JWKS fetches.

required
_owns_client bool

Whether this instance must close _client on aclose.

required
_max_fetch_attempts int

Number of total fetch attempts for transient request failures. Minimum value is 1.

2
_jwk_set_cache PyJWKSet | None

Cached parsed JWKS set.

None
_jwk_set_expiry_monotonic float

Monotonic timestamp when JWKS cache expires.

0.0
_key_cache OrderedDict[str, PyJWK]

LRU-like cache of kid to PyJWK.

OrderedDict()
_lock Lock

Async lock protecting cache state.

Lock()

Examples:

>>> from oidc_jwt_verifier import AuthConfig
>>> from oidc_jwt_verifier.async_jwks import AsyncJWKSClient
>>> config = AuthConfig(
...     issuer="https://issuer.example/",
...     audience="https://api.example",
...     jwks_url="https://issuer.example/.well-known/jwks.json",
... )
>>> client = AsyncJWKSClient.from_config(config)
>>> # await client.get_signing_key_from_jwt(token)
>>> # await client.aclose()

from_config(config: AuthConfig, *, http_client: httpx.AsyncClient | None = None, max_fetch_attempts: int = 2) -> AsyncJWKSClient classmethod

Create an async JWKS client from configuration.

Parameters:

Name Type Description Default
config AuthConfig

Auth configuration with JWKS URL/cache/timeout settings.

required
http_client AsyncClient | None

Optional externally managed client. If omitted, an internal client is created and owned by this instance.

None
max_fetch_attempts int

Total fetch attempts for request failures. Must be >= 1.

2

Returns:

Type Description
AsyncJWKSClient

Configured AsyncJWKSClient.

Raises:

Type Description
ValueError

If max_fetch_attempts < 1.

Examples:

>>> from oidc_jwt_verifier import AuthConfig
>>> config = AuthConfig(
...     issuer="https://issuer.example/",
...     audience="https://api.example",
...     jwks_url="https://issuer.example/.well-known/jwks.json",
... )
>>> client = AsyncJWKSClient.from_config(config)
Source code in oidc_jwt_verifier/async_jwks.py
@classmethod
def from_config(
    cls,
    config: AuthConfig,
    *,
    http_client: httpx.AsyncClient | None = None,
    max_fetch_attempts: int = 2,
) -> AsyncJWKSClient:
    """Create an async JWKS client from configuration.

    Args:
        config: Auth configuration with JWKS URL/cache/timeout settings.
        http_client: Optional externally managed client. If omitted,
            an internal client is created and owned by this instance.
        max_fetch_attempts: Total fetch attempts for request failures.
            Must be ``>= 1``.

    Returns:
        Configured ``AsyncJWKSClient``.

    Raises:
        ValueError: If ``max_fetch_attempts < 1``.

    Examples:
        >>> from oidc_jwt_verifier import AuthConfig
        >>> config = AuthConfig(
        ...     issuer="https://issuer.example/",
        ...     audience="https://api.example",
        ...     jwks_url="https://issuer.example/.well-known/jwks.json",
        ... )
        >>> client = AsyncJWKSClient.from_config(config)
    """
    if max_fetch_attempts < 1:
        raise ValueError("max_fetch_attempts must be >= 1")

    owns_client = http_client is None
    client = http_client or httpx.AsyncClient(timeout=config.jwks_timeout_s)
    return cls(
        _config=config,
        _client=client,
        _owns_client=owns_client,
        _max_fetch_attempts=max_fetch_attempts,
    )

aclose() -> None async

Close internal resources.

Closes the underlying HTTP client only when this instance owns it.

Examples:

>>> # await client.aclose()
Source code in oidc_jwt_verifier/async_jwks.py
async def aclose(self) -> None:
    """Close internal resources.

    Closes the underlying HTTP client only when this instance owns it.

    Examples:
        >>> # await client.aclose()
    """
    if self._owns_client:
        await self._client.aclose()

get_signing_key_from_jwt(token: str | bytes) -> PyJWK async

Resolve signing key for a JWT from configured JWKS.

Parameters:

Name Type Description Default
token str | bytes

Encoded JWT as str or ASCII-compatible bytes.

required

Returns:

Type Description
PyJWK

Matching signing key.

Raises:

Type Description
AuthError

On key lookup/fetch/parsing failures.

Examples:

>>> # signing_key = await client.get_signing_key_from_jwt(token)
Source code in oidc_jwt_verifier/async_jwks.py
async def get_signing_key_from_jwt(self, token: str | bytes) -> PyJWK:
    """Resolve signing key for a JWT from configured JWKS.

    Args:
        token: Encoded JWT as ``str`` or ASCII-compatible ``bytes``.

    Returns:
        Matching signing key.

    Raises:
        AuthError: On key lookup/fetch/parsing failures.

    Examples:
        >>> # signing_key = await client.get_signing_key_from_jwt(token)
    """
    if isinstance(token, bytes):
        try:
            token_str = token.decode("utf-8")
        except UnicodeDecodeError as exc:
            raise AuthError(
                code="jwks_error",
                message="JWKS lookup failed",
                status_code=401,
            ) from exc
    else:
        token_str = token

    kid = self._extract_kid(token_str)
    return await self.get_signing_key(kid)

get_signing_key(kid: str) -> PyJWK async

Resolve a signing key by kid.

Performs lookup against cache first, then fetches/retries JWKS when needed, including one forced refresh attempt when kid is missing.

Parameters:

Name Type Description Default
kid str

JWT key identifier.

required

Returns:

Type Description
PyJWK

Matching PyJWK.

Raises:

Type Description
AuthError

With key_not_found when no matching key exists.

Examples:

>>> # signing_key = await client.get_signing_key("kid-123")
Source code in oidc_jwt_verifier/async_jwks.py
async def get_signing_key(self, kid: str) -> PyJWK:
    """Resolve a signing key by ``kid``.

    Performs lookup against cache first, then fetches/retries JWKS when
    needed, including one forced refresh attempt when ``kid`` is missing.

    Args:
        kid: JWT key identifier.

    Returns:
        Matching ``PyJWK``.

    Raises:
        AuthError: With ``key_not_found`` when no matching key exists.

    Examples:
        >>> # signing_key = await client.get_signing_key("kid-123")
    """
    cached = await self._try_get_key_from_caches(kid)
    if cached is not None:
        return cached

    key = await self._find_key_via_jwks(kid, refresh=False)
    if key is not None:
        return key

    key = await self._find_key_via_jwks(kid, refresh=True)
    if key is not None:
        return key

    raise AuthError(
        code="key_not_found",
        message="No matching signing key",
        status_code=401,
    )

AsyncJWTVerifier(config: AuthConfig, *, jwks_client: AsyncJWKSClient | None = None, http_client: httpx.AsyncClient | None = None)

Stateful asynchronous JWT verifier for OIDC access tokens.

This verifier preserves sync-path semantics while using asynchronous JWKS fetches and key lookups.

Parameters:

Name Type Description Default
config AuthConfig

Authentication configuration.

required
jwks_client AsyncJWKSClient | None

Optional externally managed async JWKS client.

None
http_client AsyncClient | None

Optional externally managed httpx.AsyncClient used when creating an internal AsyncJWKSClient.

None

Examples:

>>> from oidc_jwt_verifier import AuthConfig
>>> from oidc_jwt_verifier.async_verifier import AsyncJWTVerifier
>>> config = AuthConfig(
...     issuer="https://example.auth0.com/",
...     audience="https://api.example.com",
...     jwks_url="https://example.auth0.com/.well-known/jwks.json",
... )
>>> verifier = AsyncJWTVerifier(config)

Initialize an async verifier.

Parameters:

Name Type Description Default
config AuthConfig

Authentication configuration.

required
jwks_client AsyncJWKSClient | None

Optional injected async JWKS client.

None
http_client AsyncClient | None

Optional injected HTTP client for internally created async JWKS client.

None
Source code in oidc_jwt_verifier/async_verifier.py
def __init__(
    self,
    config: AuthConfig,
    *,
    jwks_client: AsyncJWKSClient | None = None,
    http_client: httpx.AsyncClient | None = None,
) -> None:
    """Initialize an async verifier.

    Args:
        config: Authentication configuration.
        jwks_client: Optional injected async JWKS client.
        http_client: Optional injected HTTP client for internally created
            async JWKS client.
    """
    self._config = config
    self._decoder = jwt.PyJWT(
        options={
            "enforce_minimum_key_length": config.enforce_minimum_key_length
        }
    )
    self._jwks = jwks_client or AsyncJWKSClient.from_config(
        config, http_client=http_client
    )
    self._owns_jwks = jwks_client is None

verify_access_token(token: str) -> dict[str, Any] async

Verify a JWT access token and return its claims.

Parameters:

Name Type Description Default
token str

Encoded JWT string.

required

Returns:

Type Description
dict[str, Any]

Decoded JWT payload.

Raises:

Type Description
AuthError

On authentication or authorization failure.

Source code in oidc_jwt_verifier/async_verifier.py
async def verify_access_token(self, token: str) -> dict[str, Any]:
    """Verify a JWT access token and return its claims.

    Args:
        token: Encoded JWT string.

    Returns:
        Decoded JWT payload.

    Raises:
        AuthError: On authentication or authorization failure.
    """
    normalized = token.strip()
    if not normalized:
        raise AuthError(
            code="missing_token",
            message="Missing access token",
            status_code=401,
        )

    _, algorithm = parse_and_validate_header(
        normalized,
        allowed_algorithms=self._config.allowed_algorithms,
    )
    signing_key = await self._jwks.get_signing_key_from_jwt(normalized)

    payload = decode_and_validate_payload(
        decoder=self._decoder,
        token=normalized,
        signing_key=signing_key,
        algorithm=algorithm,
        config=self._config,
    )
    enforce_authorization_claims(payload, config=self._config)
    return payload

aclose() -> None async

Close verifier-owned async resources.

If the verifier created its own async JWKS client, this method closes that client and its owned HTTP resources.

Source code in oidc_jwt_verifier/async_verifier.py
async def aclose(self) -> None:
    """Close verifier-owned async resources.

    If the verifier created its own async JWKS client, this method closes
    that client and its owned HTTP resources.
    """
    if self._owns_jwks:
        await self._jwks.aclose()

__aenter__() -> AsyncJWTVerifier async

Enter async context manager for the verifier.

Returns:

Type Description
AsyncJWTVerifier

This verifier instance.

Source code in oidc_jwt_verifier/async_verifier.py
async def __aenter__(self) -> AsyncJWTVerifier:
    """Enter async context manager for the verifier.

    Returns:
        This verifier instance.
    """
    return self

__aexit__(_exc_type: object, _exc: object, _tb: object) -> None async

Exit async context manager and close owned resources.

Source code in oidc_jwt_verifier/async_verifier.py
async def __aexit__(
    self, _exc_type: object, _exc: object, _tb: object
) -> None:
    """Exit async context manager and close owned resources."""
    await self.aclose()

Optional FastAPI Integration API

Requires pip install "oidc-jwt-verifier[fastapi]".

  • oidc_jwt_verifier.integrations.fastapi.auth_error_to_http_exception
  • oidc_jwt_verifier.integrations.fastapi.create_async_bearer_dependency
  • oidc_jwt_verifier.integrations.fastapi.create_sync_bearer_dependency

fastapi

FastAPI integration helpers.

This module provides dependency factories that translate AuthError into fastapi.HTTPException while preserving RFC 6750 WWW-Authenticate headers.

auth_error_to_http_exception(error: AuthError, *, realm: str | None = None) -> HTTPException

Translate AuthError into fastapi.HTTPException.

Parameters:

Name Type Description Default
error AuthError

Auth error to convert.

required
realm str | None

Optional RFC 6750 realm parameter.

None

Returns:

Type Description
HTTPException

A FastAPI HTTP exception with status, detail and

HTTPException

WWW-Authenticate header.

Source code in oidc_jwt_verifier/integrations/fastapi.py
def auth_error_to_http_exception(
    error: AuthError,
    *,
    realm: str | None = None,
) -> HTTPException:
    """Translate ``AuthError`` into ``fastapi.HTTPException``.

    Args:
        error: Auth error to convert.
        realm: Optional RFC 6750 realm parameter.

    Returns:
        A FastAPI HTTP exception with status, detail and
        ``WWW-Authenticate`` header.
    """
    return HTTPException(
        status_code=error.status_code,
        detail=error.message,
        headers={
            "WWW-Authenticate": error.www_authenticate_header(realm=realm)
        },
    )

create_async_bearer_dependency(verifier: AsyncJWTVerifier, *, realm: str | None = None, auto_error: bool = False) -> Callable[[HTTPAuthorizationCredentials | None], Awaitable[dict[str, Any]]]

Create a FastAPI dependency for AsyncJWTVerifier.

Parameters:

Name Type Description Default
verifier AsyncJWTVerifier

Async verifier instance.

required
realm str | None

Optional RFC 6750 realm.

None
auto_error bool

Passed to HTTPBearer. Keep this False to let the library produce uniform AuthError mapping.

False

Returns:

Type Description
Callable[[HTTPAuthorizationCredentials | None], Awaitable[dict[str, Any]]]

A dependency callable returning decoded claims on success.

Source code in oidc_jwt_verifier/integrations/fastapi.py
def create_async_bearer_dependency(
    verifier: AsyncJWTVerifier,
    *,
    realm: str | None = None,
    auto_error: bool = False,
) -> Callable[[HTTPAuthorizationCredentials | None], Awaitable[dict[str, Any]]]:
    """Create a FastAPI dependency for ``AsyncJWTVerifier``.

    Args:
        verifier: Async verifier instance.
        realm: Optional RFC 6750 realm.
        auto_error: Passed to ``HTTPBearer``. Keep this ``False`` to let the
            library produce uniform ``AuthError`` mapping.

    Returns:
        A dependency callable returning decoded claims on success.
    """
    security = HTTPBearer(auto_error=auto_error)

    async def dependency(
        credentials: HTTPAuthorizationCredentials | None = Depends(security),  # noqa: B008
    ) -> dict[str, Any]:
        """Resolve and verify bearer token for one request.

        Args:
            credentials: FastAPI bearer credentials.

        Returns:
            Decoded token claims.

        Raises:
            HTTPException: When authentication/authorization fails.
        """
        token = credentials.credentials if credentials is not None else ""
        try:
            return await verifier.verify_access_token(token)
        except AuthError as exc:
            raise auth_error_to_http_exception(exc, realm=realm) from exc

    return dependency

create_sync_bearer_dependency(verifier: JWTVerifier, *, realm: str | None = None, offload_to_threadpool: bool = True, auto_error: bool = False) -> Callable[[HTTPAuthorizationCredentials | None], Awaitable[dict[str, Any]]]

Create a FastAPI dependency for sync JWTVerifier.

Parameters:

Name Type Description Default
verifier JWTVerifier

Sync verifier instance.

required
realm str | None

Optional RFC 6750 realm.

None
offload_to_threadpool bool

Whether to run sync verification in run_in_threadpool. Set to False only when keys are already cached or otherwise guaranteed local; if JWKS must be fetched over HTTP, sync I/O will block the event loop.

True
auto_error bool

Passed to HTTPBearer.

False

Returns:

Type Description
Callable[[HTTPAuthorizationCredentials | None], Awaitable[dict[str, Any]]]

A dependency callable returning decoded claims on success.

Source code in oidc_jwt_verifier/integrations/fastapi.py
def create_sync_bearer_dependency(
    verifier: JWTVerifier,
    *,
    realm: str | None = None,
    offload_to_threadpool: bool = True,
    auto_error: bool = False,
) -> Callable[[HTTPAuthorizationCredentials | None], Awaitable[dict[str, Any]]]:
    """Create a FastAPI dependency for sync ``JWTVerifier``.

    Args:
        verifier: Sync verifier instance.
        realm: Optional RFC 6750 realm.
        offload_to_threadpool: Whether to run sync verification in
            ``run_in_threadpool``. Set to ``False`` only when keys are already
            cached or otherwise guaranteed local; if JWKS must be fetched over
            HTTP, sync I/O will block the event loop.
        auto_error: Passed to ``HTTPBearer``.

    Returns:
        A dependency callable returning decoded claims on success.
    """
    security = HTTPBearer(auto_error=auto_error)

    async def dependency(
        credentials: HTTPAuthorizationCredentials | None = Depends(security),  # noqa: B008
    ) -> dict[str, Any]:
        """Resolve and verify bearer token for one request.

        Args:
            credentials: FastAPI bearer credentials.

        Returns:
            Decoded token claims.

        Raises:
            HTTPException: When authentication/authorization fails.
        """
        token = credentials.credentials if credentials is not None else ""
        try:
            if offload_to_threadpool:
                return cast(
                    "dict[str, Any]",
                    await run_in_threadpool(
                        verifier.verify_access_token, token
                    ),
                )
            return verifier.verify_access_token(token)
        except AuthError as exc:
            raise auth_error_to_http_exception(exc, realm=realm) from exc

    return dependency

Optional Starlette Integration API

Requires pip install "oidc-jwt-verifier[starlette]".

  • oidc_jwt_verifier.integrations.starlette.BearerAuthMiddleware
  • oidc_jwt_verifier.integrations.starlette.verify_request_bearer_token
  • oidc_jwt_verifier.integrations.starlette.auth_error_to_response

starlette

Starlette integration helpers.

This module offers middleware and helper functions to apply verifier logic in Starlette applications while preserving RFC 6750 response semantics.

Returns:

Type Description

None.

Examples:

>>> from oidc_jwt_verifier.integrations.starlette import (
...     BearerAuthMiddleware,
... )

BearerAuthMiddleware(app: ASGIApp, *, verifier: JWTVerifier | AsyncJWTVerifier, realm: str | None = None, exempt_paths: set[str] | None = None, claims_state_key: str = 'auth_claims')

Starlette middleware that verifies bearer access tokens.

Valid claims are stored in request.state under claims_state_key.

Parameters:

Name Type Description Default
app ASGIApp

Downstream ASGI app.

required
verifier JWTVerifier | AsyncJWTVerifier

Sync or async verifier.

required
realm str | None

Optional realm for RFC 6750 header generation.

None
exempt_paths set[str] | None

Paths to skip authentication for.

None
claims_state_key str

Key used in request.state for decoded claims.

'auth_claims'

Returns:

Type Description

None.

Examples:

>>> from starlette.applications import Starlette
>>> app = Starlette()
>>> _ = BearerAuthMiddleware(app, verifier=verifier)

Initialize middleware configuration.

Source code in oidc_jwt_verifier/integrations/starlette.py
def __init__(
    self,
    app: ASGIApp,
    *,
    verifier: JWTVerifier | AsyncJWTVerifier,
    realm: str | None = None,
    exempt_paths: set[str] | None = None,
    claims_state_key: str = "auth_claims",
) -> None:
    """Initialize middleware configuration."""
    self._app = app
    self._verifier = verifier
    self._realm = realm
    self._exempt_paths = exempt_paths or set()
    self._claims_state_key = claims_state_key

__call__(scope: Scope, receive: Receive, send: Send) -> None async

Process request authentication.

Parameters:

Name Type Description Default
scope Scope

ASGI scope.

required
receive Receive

ASGI receive callable.

required
send Send

ASGI send callable.

required

Returns:

Type Description
None

None.

Examples:

>>> # Invoked by Starlette's ASGI runtime, not called directly.
>>> # await middleware(scope, receive, send)
Source code in oidc_jwt_verifier/integrations/starlette.py
async def __call__(
    self, scope: Scope, receive: Receive, send: Send
) -> None:
    """Process request authentication.

    Args:
        scope: ASGI scope.
        receive: ASGI receive callable.
        send: ASGI send callable.

    Returns:
        None.

    Raises:
        None. ``AuthError`` is handled and converted into an RFC 6750 response.

    Examples:
        >>> # Invoked by Starlette's ASGI runtime, not called directly.
        >>> # await middleware(scope, receive, send)
    """
    if scope.get("type") != "http":
        await self._app(scope, receive, send)
        return

    if scope.get("path") in self._exempt_paths:
        await self._app(scope, receive, send)
        return

    request = Request(scope, receive=receive)
    try:
        claims = await verify_request_bearer_token(
            request, verifier=self._verifier
        )
    except AuthError as exc:
        response = auth_error_to_response(exc, realm=self._realm)
        await response(scope, receive, send)
        return

    state = scope.setdefault("state", {})
    if isinstance(state, dict):
        state[self._claims_state_key] = claims

    await self._app(scope, receive, send)

auth_error_to_response(error: AuthError, *, realm: str | None = None) -> JSONResponse

Convert AuthError into a Starlette JSON response.

Parameters:

Name Type Description Default
error AuthError

Auth error to convert.

required
realm str | None

Optional realm for WWW-Authenticate header.

None

Returns:

Type Description
JSONResponse

JSON response with correct status and RFC 6750 header.

Examples:

>>> from oidc_jwt_verifier.errors import AuthError
>>> error = AuthError(code="invalid_token", message="bad token")
>>> response = auth_error_to_response(error)
>>> response.status_code
401
Source code in oidc_jwt_verifier/integrations/starlette.py
def auth_error_to_response(
    error: AuthError,
    *,
    realm: str | None = None,
) -> JSONResponse:
    """Convert ``AuthError`` into a Starlette JSON response.

    Args:
        error: Auth error to convert.
        realm: Optional realm for ``WWW-Authenticate`` header.

    Returns:
        JSON response with correct status and RFC 6750 header.

    Raises:
        None.

    Examples:
        >>> from oidc_jwt_verifier.errors import AuthError
        >>> error = AuthError(code="invalid_token", message="bad token")
        >>> response = auth_error_to_response(error)
        >>> response.status_code
        401
    """
    return JSONResponse(
        {"detail": error.message, "code": error.code},
        status_code=error.status_code,
        headers={
            "WWW-Authenticate": error.www_authenticate_header(realm=realm)
        },
    )

extract_bearer_token(authorization_header: str | None) -> str

Extract a bearer token from Authorization header value.

Parameters:

Name Type Description Default
authorization_header str | None

Raw header value.

required

Returns:

Type Description
str

Bearer token string, or empty string when missing/invalid.

Examples:

>>> extract_bearer_token("Bearer abc.def.ghi")
'abc.def.ghi'
>>> extract_bearer_token("Basic abc")
''
Source code in oidc_jwt_verifier/integrations/starlette.py
def extract_bearer_token(authorization_header: str | None) -> str:
    """Extract a bearer token from ``Authorization`` header value.

    Args:
        authorization_header: Raw header value.

    Returns:
        Bearer token string, or empty string when missing/invalid.

    Raises:
        None.

    Examples:
        >>> extract_bearer_token("Bearer abc.def.ghi")
        'abc.def.ghi'
        >>> extract_bearer_token("Basic abc")
        ''
    """
    if authorization_header is None:
        return ""
    parts = authorization_header.split(" ", 1)
    if len(parts) != 2 or parts[0].lower() != "bearer":
        return ""
    return parts[1].strip()

verify_request_bearer_token(request: Request, *, verifier: JWTVerifier | AsyncJWTVerifier) -> dict[str, Any] async

Verify bearer token from a Starlette request.

Parameters:

Name Type Description Default
request Request

Incoming request.

required
verifier JWTVerifier | AsyncJWTVerifier

Sync or async verifier instance.

required

Returns:

Type Description
dict[str, Any]

Decoded JWT claims.

Raises:

Type Description
AuthError

On authentication/authorization failure.

Examples:

>>> # Usually called from middleware with a Starlette ``Request``.
>>> # claims = await verify_request_bearer_token(request, verifier=verifier)
Source code in oidc_jwt_verifier/integrations/starlette.py
async def verify_request_bearer_token(
    request: Request,
    *,
    verifier: JWTVerifier | AsyncJWTVerifier,
) -> dict[str, Any]:
    """Verify bearer token from a Starlette request.

    Args:
        request: Incoming request.
        verifier: Sync or async verifier instance.

    Returns:
        Decoded JWT claims.

    Raises:
        AuthError: On authentication/authorization failure.

    Examples:
        >>> # Usually called from middleware with a Starlette ``Request``.
        >>> # claims = await verify_request_bearer_token(request, verifier=verifier)
    """
    token = extract_bearer_token(request.headers.get("Authorization"))
    if isinstance(verifier, JWTVerifier):
        return cast(
            "dict[str, Any]",
            await run_in_threadpool(verifier.verify_access_token, token),
        )
    return await verifier.verify_access_token(token)