Jwks

Validate a JWT against a JWKS (JSON Web Key Set).

  1. Overview
  2. jwks_url
  3. audience
  4. issuer
  5. algorithms
  6. jwks_cache_time
  7. authorization_url
  8. claims
  9. documentation_security_name

Overview

This authentication class fetches a set of public keys from a remote JWKS endpoint and uses them to verify the signature of a Bearer token presented in the Authorization header. It is the standard way to validate JWTs issued by OAuth 2.0 / OIDC providers such as Auth0, Azure Active Directory, or any provider that publishes a JWKS document.

The minimum configuration is a jwks_url. You will typically also want to set audience and issuer to ensure the token was issued for your application:

import clearskies

wsgi = clearskies.contexts.WsgiRef(
    clearskies.endpoints.Callable(
        lambda authorization_data: authorization_data,
        authentication=clearskies.authentication.Jwks(
            jwks_url="https://auth.example.com/.well-known/jwks.json",
            audience="https://api.example.com/",
            issuer="https://auth.example.com/",
        ),
    )
)
wsgi()

Calling the endpoint without a valid token returns a 401:

$ curl http://localhost:8080 | jq
{
    "status": "client_error",
    "error": "Not Authenticated",
    "data": [],
    "pagination": {},
    "input_errors": {}
}

With a valid Bearer token the decoded JWT payload is available via the authorization_data dependency:

$ curl http://localhost:8080 -H "Authorization: Bearer <token>" | jq
{
    "status": "success",
    "error": "",
    "data": {
        "sub": "example-user",
        "iss": "https://auth.example.com/",
        "aud": "https://api.example.com/"
    },
    "pagination": {},
    "input_errors": {}
}

jwks_url

Required

The URL from which to fetch the JSON Web Key Set.

clearskies fetches this URL to retrieve the public keys used to verify incoming JWTs. The response must be a standard JWKS document — a JSON object with a keys array. Key selection is performed automatically by matching the kid header of the incoming token against the keys in the set.

The key set is cached for jwks_cache_time seconds (default one day) to avoid a remote fetch on every request.

audience

Optional

The audience to accept JWTs for.

If provided, JWTs will be rejected unless their aud claim contains this value. You should always set this to the identifier of your own API to prevent tokens issued for other services from being accepted. If you do not provide an audience then all audiences will be accepted.

issuer

Optional

The expected issuer of the JWTs.

If provided, JWTs will be rejected unless their iss claim exactly matches this value. Set this to the base URL of the authorization server that issues tokens for your application. If you do not provide an issuer then any issuer will be accepted.

algorithms

Optional

The allowed signing algorithms.

Passed directly to jwcrypto.jwt.JWT(algs=...). Any token whose alg header is not in this list is rejected before signature verification. An empty list disables the restriction; any algorithm that is cryptographically compatible with the matched JWKS key is then accepted.

jwks_cache_time

Optional

The number of seconds for which the JWKS URL contents can be cached.

Defaults to 86400 (one day). Set to 0 to disable caching and always fetch fresh keys.

authorization_url

Optional

The authorization URL used in the auto-generated API documentation.

This value is only used when generating OpenAPI documentation and has no effect on authentication behaviour at runtime.

claims

Optional

Additional claim validation applied after the JWT signature is verified.

Provide a list of claim names to require their presence in the decoded payload. A 401 is returned if any listed claim is missing:

import clearskies

wsgi = clearskies.contexts.WsgiRef(
    clearskies.endpoints.Callable(
        lambda: {"hello": "world"},
        authentication=clearskies.authentication.Jwks(
            jwks_url="https://example.com/.well-known/jwks.json",
            claims=["role", "sub"],
        ),
    )
)
wsgi()

For custom logic, provide a callable instead. The clearskies DI system injects jwt_claims (the decoded payload as a dict) plus any other name resolvable from the DI container, so only declare the parameters your function actually needs.

The callable may return True or None to accept the token, False to reject it with a 401, or a list of strings to dynamically specify which claims must be present:

import clearskies

def no_blocked_users(jwt_claims):
    return "blocked" not in jwt_claims

wsgi = clearskies.contexts.WsgiRef(
    clearskies.endpoints.Callable(
        lambda: {"hello": "world"},
        authentication=clearskies.authentication.Jwks(
            jwks_url="https://example.com/.well-known/jwks.json",
            claims=no_blocked_users,
        ),
    )
)
wsgi()

You can also return a dynamic list of required claims based on the payload:

import clearskies

def require_extra_claims_for_admins(jwt_claims):
    if jwt_claims.get("role") == "admin":
        return ["department", "employee_id"]
    return True

wsgi = clearskies.contexts.WsgiRef(
    clearskies.endpoints.Callable(
        lambda: {"hello": "world"},
        authentication=clearskies.authentication.Jwks(
            jwks_url="https://example.com/.well-known/jwks.json",
            claims=require_extra_claims_for_admins,
        ),
    )
)
wsgi()

documentation_security_name

Optional

The name of the security scheme in the auto-generated API documentation.

Defaults to jwt. Override this if your documentation needs to distinguish between multiple JWT-based authentication schemes on the same set of endpoints.