NAME

Catalyst::Plugin::OAuth2::AuthorizationServer::Server - Pure-logic OAuth 2.1 Authorization Server engine

DESCRIPTION

The pure-logic OAuth 2.1 engine behind the Catalyst seam. Holds the Store reference, the signing key, and configuration; is otherwise stateless (the only mutable state lives in the injected Store).

Access tokens are signed with a symmetric HMAC algorithm only: jwt_alg may be HS256 (the default), HS384 or HS512. Asymmetric signing (RS*, ES*, PS*) and alg=none are not supported, and no JWKS is published: this is deliberate for the MCP single-server profile, where the Authorization Server and Resource Server share one deployment and one key. signing_key must be at least as long as the algorithm's hash output (32, 48 or 64 bytes respectively, per RFC 7518 3.2); a shorter key is rejected at construction.

METHODS

mint_access_token( \%claims, $aud )

Mint a signed JWT access token. The engine stamps iss, aud, iat, exp and jti, and they are stamped after \%claims, so a caller cannot override them. $aud defaults to the configured resource list. Returns the encoded JWT string.

register_client( \%metadata )

Dynamic Client Registration (RFC 7591). Validates redirect_uris (must be present; each must be HTTPS or loopback HTTP; no fragments; within length limits). Generates a client_id, calls Store::create_client, and returns the stored client hashref.

Per RFC 7591 3.2.1, registration also rejects metadata asking for anything this AS does not support, with invalid_client_metadata. The allow-lists are taken from "metadata_document", so registration can never accept a value the discovery document does not advertise:

  • token_endpoint_auth_method must be one of token_endpoint_auth_methods_supported (none: this profile registers public PKCE clients, so client_secret_basic and friends are rejected).

  • grant_types must be a non-empty arrayref, each value one of grant_types_supported (authorization_code, refresh_token).

  • response_types must be a non-empty arrayref, each value one of response_types_supported (code).

  • scope is checked against scopes_supported only when the AS is configured with one; with no scopes_supported the server declares no constraint, so scope is left free-form.

Everything else RFC 7591 leaves free-form (client_name, client_uri, logo_uri, contacts, software_id, extension fields) is stored as given and never rejected merely for being present.

validate_authorize( \%params )

Validate an authorization request (RFC 6749 4.1.1 + PKCE RFC 7636). Checks client, redirect_uri, response_type, code_challenge (must be a 43-character base64url string), code_challenge_method (must be S256), scope, and resource. On success, stashes the request via Store::save_authorization_request and returns { request_id }.

issue_code( $subject, $request_id )

Atomically consume the stashed authorization request and mint a single-use authorization code bound to $subject. Returns { code, redirect_uri, state }. Throws invalid_request if the request is unknown or expired.

exchange_authorization_code( \%params )

Authorization-code grant (RFC 6749 4.1.3). Validates code, redirect_uri, client_id binding, and PKCE verifier. Returns { access_token, token_type, expires_in, refresh_token } (plus scope if the request carried one).

refresh( \%params )

Refresh-token grant (RFC 6749 6). Rotates the refresh token via Store::rotate_refresh_token and mints a new access + refresh pair. Optionally validates a client_id parameter against the binding.

metadata_document

Return the RFC 8414 Authorization Server Metadata hashref.