RFC9101: The OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request (JAR)

This section contains the generic implementation of RFC9101.

This specification describe how to pass the authorization request payload in a JWT (called request object) instead of directly instead of GET or POST params.

The request object can either be passed directly in a request parameter, or be hosted by the client and be passed by reference with a request_uri parameter. By reference, the Authorization Server fetches the request object itself:

        sequenceDiagram
    autonumber
    participant User as User's browser<br/>(User Agent)
    participant Client
    box rgba(80, 120, 220, 0.12) JWTAuthenticationRequest
        participant AS as Authorization Server
    end

    Note over Client: Signs a request object
    Client-->>User: 302 Found → /authorize
    User->>AS: GET /authorize (request or request_uri)
    opt By reference
        AS->>Client: GET request_uri
        Client-->>AS: 200 OK + request object
    end
    Note over AS: Verifies the request object
    Note over User,AS: The user logs in and approves
    AS-->>User: 302 Found → redirect_uri?code=…
    
  1. The client signs a JWT that contains the authorization request parameters. It redirects the browser to the authorization endpoint.

  2. The request contains client_id, and either the JWT as request, or its URL as request_uri. JWTAuthenticationRequest rejects the request if both are present.

  3. With request_uri only. validate_request_uri decides whether the server may fetch this URL for this client. It rejects every URL by default. get_request_object then fetches the JWT. See Retrieving request objects.

  4. The client returns the JWT.

  5. In both cases, JWTAuthenticationRequest checks the JWT signature with the client key, the signing algorithm, and that the client_id claim matches the client_id parameter. The claims of the JWT then replace the request parameters, and the authorization flow goes on as usual.

The /authorize path is an example.

This usage is more secure than passing the request payload directly in the request, read the RFC to know all the details.

Request objects are optional, unless it is enforced by clients with the require_signed_request_object client metadata, or server-wide with the require_signed_request_object server metadata.

Signature policy

When either the server or client requires signed request objects, alg=none is rejected even if it appears in the server’s supported algorithm list or is returned by get_request_object_signing_algorithms(). Unsigned request objects remain supported when signatures are optional and the algorithm configuration permits none.

Retrieving request objects

Implement validate_request_uri(request_uri, client) with an explicit policy for the request objects your server can safely retrieve. The default rejects all URIs, including URIs present in client metadata. Validation runs before get_request_object() is called; rejected URIs return invalid_request_uri:

class JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest):
    def validate_request_uri(self, request_uri, client):
        return is_approved_request_uri(request_uri, client)

is_approved_request_uri is an application function: it must authorize the reference for this client and enforce the destination restrictions described below. Merely checking whether a URI was supplied by a client is insufficient. Applications using references to locally stored objects, such as URNs, must validate that those objects belong to the requesting client.

The generic JAR extension does not implement OIDC URI registration. OIDC providers should use authlib.oidc.core.OpenIDJWTAuthenticationRequest; see OpenID request objects. It checks require_request_uri_registration and registered request_uris independently of the application’s retrieval policy.

For network retrieval, validate destinations during URI registration. Untrusted client metadata is not an approval policy. Require HTTPS, reject credentials in URLs, and restrict both hostnames and the IP addresses actually used for outbound connections. Block loopback, private, link-local, and other non-public destinations. Checking DNS once before an unrestricted HTTP request does not prevent DNS rebinding: the transport or network must enforce the destination restrictions.

Disable redirects, set connection and read timeouts, enforce an overall retrieval deadline and response size limit, and require the response media type application/oauth-authz-req+jwt. The get_request_object() API documentation below includes an example of HTTP response limits. These safeguards apply even to registered URIs, whose DNS records can change. JWT signature validation occurs after retrieval and cannot prevent SSRF from the retrieval itself.

API Reference

class authlib.oauth2.rfc9101.JWTAuthenticationRequest(support_request: bool = True, support_request_uri: bool = True)

Authorization server extension implementing the support for JWT secured authentication request, as defined in RFC9101.

Parameters:
  • support_request – Whether to enable support for the request parameter.

  • support_request_uri – Whether to enable support for the request_uri parameter.

This extension is intended to be inherited and registered into the authorization server:

class JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest):
    def resolve_client_public_key(self, client: ClientMixin):
        return get_jwks_for_client(client)

    def validate_request_uri(self, request_uri: str, client: ClientMixin):
        return is_approved_request_uri(request_uri, client)

    def get_request_object(self, request_uri: str):
        # Apply the retrieval safeguards documented in get_request_object.
        return fetch_approved_request_object(request_uri)

    def get_server_metadata(self):
        return {
            "issuer": ...,
            "authorization_endpoint": ...,
            "require_signed_request_object": ...,
        }

    def get_client_require_signed_request_object(self, client: ClientMixin):
        return client.require_signed_request_object


authorization_server.register_extension(JWTAuthenticationRequest())
get_request_object_signing_algorithms(client)

Return the supported algorithms for verifying the request_object JWT signature. By default, this method will only return the recommended algorithms. If signed request object is not required, “none” algorithm will be included. The signature requirement is enforced independently, even if this method returns “none”.

Developers can override this method to customize the supported algorithms:

def get_request_object_signing_algorithms(self, client):
    return ["RS256"]
validate_request_uri(request_uri: str, client: ClientMixin) → bool

Return whether request_uri may be retrieved for this client.

Called before get_request_object(). The default rejects every URI; applications supporting request_uri must implement a secure policy. For network retrieval, prevent SSRF, including private destinations and DNS rebinding. For references to locally stored request objects, validate that the object belongs to the requesting client.

OIDC URI registration is handled by authlib.oidc.core.JWTAuthenticationRequest independently of this application policy.

get_request_object(request_uri: str)

Retrieve the request object at an approved request_uri.

This method must be implemented if request_uri is supported. For network retrieval, enforce HTTPS and restrict outbound connections to approved public destinations, including the resolved IP addresses. Do not follow redirects. Limit response size and retrieval time, and check the response media type, as described in RFC 9101 Section 10.4.1.

The following example adds HTTP response safeguards. The application’s HTTP transport or network policy must also block private destinations and prevent DNS rebinding:

def get_request_object(self, request_uri: str):
    try:
        with requests.get(
            request_uri,
            timeout=(3, 5),
            allow_redirects=False,
            stream=True,
            headers={"Accept": "application/oauth-authz-req+jwt"},
        ) as response:
            media_type = response.headers.get("Content-Type", "").split(
                ";", 1
            )[0]
            if (
                response.status_code != 200
                or media_type != "application/oauth-authz-req+jwt"
            ):
                return None
            content = bytearray()
            for chunk in response.iter_content(chunk_size=4096):
                content.extend(chunk)
                if len(content) > 262144:
                    return None
            return content.decode("utf-8")
    except (requests.RequestException, UnicodeError):
        return None

A timeout between reads is not an overall deadline. Also enforce an overall retrieval deadline in the transport or application.

resolve_client_public_key(client: ClientMixin)

Resolve the client public key for verifying the JWT signature. A client may have many public keys, in this case, we can retrieve it via kid value in headers. Developers MUST implement this method:

from joserfc import KeySet


class JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest):
    def resolve_client_public_key(self, client):
        if client.jwks_uri:
            data = requests.get(client.jwks_uri).json()
            return KeySet.import_key_set(data)

        return KeySet.import_key_set(client.jwks)
get_server_metadata() → dict

Return server metadata which includes supported grant types, response types and etc.

When the require_signed_request_object claim is True, all clients require that authorization requests use request objects, and an error will be returned when the authorization request payload is passed in the request body or query string:

class JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest):
    def get_server_metadata(self):
        return {
            "issuer": ...,
            "authorization_endpoint": ...,
            "require_signed_request_object": ...,
            "request_object_signing_alg_values_supported": ["RS256", ...],
        }
get_client_require_signed_request_object(client: ClientMixin) → bool

Return the ‘require_signed_request_object’ client metadata.

When True, the client requires that authorization requests use request objects, and an error will be returned when the authorization request payload is passed in the request body or query string:

class JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest):
    def get_client_require_signed_request_object(self, client):
        return client.require_signed_request_object

If not implemented, the value is considered as False.

class authlib.oauth2.rfc9101.ClientMetadataClaims(claims: dict[str, Any], header: dict[str, Any], options: dict[str, ClaimsOption] | None = None, params: dict[str, Any] = None)

Additional client metadata can be used with RFC7591: OAuth 2.0 Dynamic Client Registration Protocol and RFC7592: OAuth 2.0 Dynamic Client Registration Management Protocol endpoints.

This can be used with:

server.register_endpoint(
    ClientRegistrationEndpoint(
        claims_classes=[
            rfc7591.ClientMetadataClaims,
            rfc9101.ClientMetadataClaims,
        ]
    )
)

server.register_endpoint(
    ClientRegistrationEndpoint(
        claims_classes=[
            rfc7591.ClientMetadataClaims,
            rfc9101.ClientMetadataClaims,
        ]
    )
)
class authlib.oauth2.rfc9101.AuthorizationServerMetadata

Authorization Server Metadata extension for RFC9101 (JAR).

This class can be used with validate() to validate JAR-specific metadata:

from authlib.oauth2 import rfc8414, rfc9101

metadata = rfc8414.AuthorizationServerMetadata(data)
metadata.validate(metadata_classes=[rfc9101.AuthorizationServerMetadata])
validate_require_signed_request_object()

Indicates where authorization request needs to be protected as Request Object and provided through either request or request_uri parameter.