barrel_mcp_client_auth_oauth (barrel_mcp v2.0.2)
View SourceOAuth 2.1 + PKCE authorization for barrel_mcp_client.
Implements the MCP authorization flow described in the spec and the underlying RFCs:
- RFC 9728 — Protected Resource Metadata (PRM)
- RFC 8414 — Authorization Server Metadata
- RFC 7636 — PKCE (S256)
- RFC 8707 —
resourceindicator on auth + token requests - RFC 6749 / OAuth 2.1 — authorization-code + refresh_token grants
What this module does
Two responsibilities, kept separate so hosts can mix them as they need:
- **Discovery helpers** that hosts use during initial token acquisition: parse
WWW-Authenticate, fetch PRM, fetch AS metadata, build authorization URLs with PKCE, exchange the returned code at the token endpoint. - **
barrel_mcp_client_authbehaviour implementation** that attaches theAuthorization: Bearer ...header on every outgoing request and refreshes the token automatically on 401 (when arefresh_tokenwas supplied).
What this module does NOT do
The authorization-code redirect step requires a browser and a local listener to capture the callback — that's a host concern, not a library one. Hosts run the interactive step however suits them (open a URL, do a CLI device-code flow, paste a code), then pass the resulting tokens back via the {oauth, Config} tuple. The library handles refresh from there.
Config shape
{oauth, #{
access_token := binary(), %% required
refresh_token => binary(), %% optional; enables refresh
token_endpoint => binary(), %% required if refresh_token set
client_id => binary(), %% required if refresh_token set
client_secret => binary(), %% optional confidential client
resource => binary(), %% RFC 8707 canonical id
scopes => [binary()] %% optional
}}
Summary
Functions
Build an authorization-code+PKCE URL for the user to visit. Params must include client_id and redirect_uri; the function handles code_challenge/code_challenge_method for you given the verifier. state is generated automatically if not supplied.
Acquire an access token via the OAuth 2.1 client_credentials grant — for unattended / machine-to-machine flows where there is no human in the loop. Per the MCP ext-auth OAuth Client Credentials extension, callers may authenticate either with a client_secret (HTTP Basic, per RFC 6749) or a client_assertion (private_key_jwt per RFC 7523).
Derive the S256 code challenge for a verifier.
Fetch the Authorization Server Metadata for the given issuer URL. Tries /.well-known/oauth-authorization-server first, then falls back to /.well-known/openid-configuration.
Fetch and parse the Protected Resource Metadata document.
Exchange an authorization code for tokens.
Generate a 64-byte random URL-safe code verifier (RFC 7636).
RFC 7523 JWT Bearer access-token request. The second step of the EMA chain: present the ID-JAG to the MCP server's authorization-server token endpoint and receive a short-lived access token.
Extract the resource_metadata URL from a WWW-Authenticate header per RFC 9728. Returns undefined if not present.
Refresh an access token via the refresh_token grant.
Dynamic Client Registration ([RFC 7591][rfc7591]). Posts the supplied client metadata to the AS's registration_endpoint and returns the AS's response unchanged: typically including client_id, optionally client_secret, client_id_issued_at, client_secret_expires_at, plus any client-metadata echo the AS chose to include.
Variant of register_client/2 that accepts an options map. Currently the only option is initial_access_token (RFC 7591 section 3): an opaque bearer token issued out of band by the AS to gate registration. When present, the call adds Authorization: Bearer <token>.
RFC 8693 OAuth 2.0 Token Exchange. Used by the MCP ext-auth Enterprise-Managed Authorization extension to exchange an IdP-issued ID Token (or SAML assertion) for an Identity Assertion JWT Authorization Grant (the "ID-JAG"), scoped to a specific MCP server resource.
Types
-type config() :: #{access_token := binary(), refresh_token => binary(), token_endpoint => binary(), client_id => binary(), client_secret => binary(), resource => binary(), scopes => [binary()]} | client_credentials_config() | enterprise_managed_config().
-type enterprise_managed_config() :: #{grant_type := enterprise_managed, idp_token_endpoint := binary(), as_token_endpoint := binary(), client_id := binary(), client_secret => binary(), client_assertion => binary(), subject_token := binary(), subject_token_type := binary(), audience := binary(), resource := binary(), scopes => [binary()]}.
-type handle() :: #h{access_token :: binary() | undefined, refresh_token :: binary() | undefined, token_endpoint :: binary() | undefined, client_id :: binary() | undefined, client_secret :: binary() | undefined, client_assertion :: binary() | undefined, resource :: binary() | undefined, scopes :: [binary()] | undefined, mode :: auth_code | client_credentials | enterprise_managed, idp_token_endpoint :: binary() | undefined, subject_token :: binary() | undefined, subject_token_type :: binary() | undefined, audience :: binary() | undefined}.
Functions
Build an authorization-code+PKCE URL for the user to visit. Params must include client_id and redirect_uri; the function handles code_challenge/code_challenge_method for you given the verifier. state is generated automatically if not supplied.
Acquire an access token via the OAuth 2.1 client_credentials grant — for unattended / machine-to-machine flows where there is no human in the loop. Per the MCP ext-auth OAuth Client Credentials extension, callers may authenticate either with a client_secret (HTTP Basic, per RFC 6749) or a client_assertion (private_key_jwt per RFC 7523).
Derive the S256 code challenge for a verifier.
Fetch the Authorization Server Metadata for the given issuer URL. Tries /.well-known/oauth-authorization-server first, then falls back to /.well-known/openid-configuration.
Fetch and parse the Protected Resource Metadata document.
Exchange an authorization code for tokens.
-spec gen_code_verifier() -> binary().
Generate a 64-byte random URL-safe code verifier (RFC 7636).
RFC 7523 JWT Bearer access-token request. The second step of the EMA chain: present the ID-JAG to the MCP server's authorization-server token endpoint and receive a short-lived access token.
Extract the resource_metadata URL from a WWW-Authenticate header per RFC 9728. Returns undefined if not present.
Refresh an access token via the refresh_token grant.
-spec register_client(RegistrationEndpoint :: binary(), Metadata :: map()) -> {ok, ClientInfo :: map()} | {error, term()}.
Dynamic Client Registration ([RFC 7591][rfc7591]). Posts the supplied client metadata to the AS's registration_endpoint and returns the AS's response unchanged: typically including client_id, optionally client_secret, client_id_issued_at, client_secret_expires_at, plus any client-metadata echo the AS chose to include.
Hosts that receive a fresh client_id (and client_secret, if issued) feed it into a subsequent {oauth, ...}, {oauth_client_credentials, ...}, or {oauth_enterprise, ...} connect spec. This stays a standalone exchanger; auto-wiring would require persisting credentials, which is host policy.
[rfc7591]: https://datatracker.ietf.org/doc/html/rfc7591
-spec register_client(RegistrationEndpoint :: binary(), Metadata :: map(), Opts :: #{initial_access_token => binary(), _ => _}) -> {ok, ClientInfo :: map()} | {error, term()}.
Variant of register_client/2 that accepts an options map. Currently the only option is initial_access_token (RFC 7591 section 3): an opaque bearer token issued out of band by the AS to gate registration. When present, the call adds Authorization: Bearer <token>.
RFC 8693 OAuth 2.0 Token Exchange. Used by the MCP ext-auth Enterprise-Managed Authorization extension to exchange an IdP-issued ID Token (or SAML assertion) for an Identity Assertion JWT Authorization Grant (the "ID-JAG"), scoped to a specific MCP server resource.
Returns {ok, IdJag} where IdJag is the binary token extracted from the response's access_token field, or an error describing the failure. A 4xx with invalid_grant surfaces the typed {error, subject_token_expired} (the RFC 8693 error semantic for an expired or revoked subject token).