ExMCP.Authorization (ex_mcp v0.9.0)
View SourceMCP Authorization support for OAuth 2.1 with PKCE.
This module provides OAuth 2.1 authorization flows as specified in the MCP Authorization specification. It supports both authorization code flow (for user-based interactions) and client credentials flow (for application-to-application communication).
Authorization is optional for MCP implementations. When supported:
- HTTP-based transports should conform to this specification
- STDIO transports should retrieve credentials from the environment
- Other transports must follow established security best practices
Security Features
- PKCE (Proof Key for Code Exchange) - Required for all authorization code flows
- HTTPS enforcement - All authorization endpoints must use HTTPS (except localhost)
- Secure redirect URI validation - Prevents open redirect vulnerabilities
- Dynamic client registration - Automatic client ID acquisition
- Server metadata discovery - RFC 8414 OAuth 2.0 Authorization Server Metadata
Quick Start
Authorization Code Flow (User-based)
# Start authorization flow
{:ok, auth_url, state} = ExMCP.Authorization.start_authorization_flow(%{
client_id: "my-client",
redirect_uri: "http://localhost:8080/callback",
authorization_endpoint: "https://auth.example.com/oauth/authorize",
scopes: ["mcp:read", "mcp:write"]
})
# User visits auth_url and authorizes
# Server redirects to redirect_uri with authorization code
# Exchange code for token
{:ok, token_response} = ExMCP.Authorization.exchange_code_for_token(%{
code: "auth_code_from_callback",
code_verifier: state.code_verifier,
client_id: "my-client",
redirect_uri: "http://localhost:8080/callback",
token_endpoint: "https://auth.example.com/oauth/token"
})Client Credentials Flow (Application-to-application)
{:ok, token_response} = ExMCP.Authorization.client_credentials_flow(%{
client_id: "service-client",
client_secret: "client-secret",
token_endpoint: "https://auth.example.com/oauth/token",
scopes: ["mcp:admin"]
})Server Metadata Discovery
# Discover endpoints automatically
{:ok, metadata} = ExMCP.Authorization.discover_server_metadata(
"https://auth.example.com"
)Client Configuration
For automatic authorization handling in MCP clients:
{:ok, client} = ExMCP.Client.start_link(
transport: :http,
url: "https://api.example.com",
auth_config: %{
client_id: "my-client",
client_secret: "secret", # Optional for public clients
token_endpoint: "https://auth.example.com/token",
authorization_endpoint: "https://auth.example.com/authorize",
redirect_uri: "http://localhost:8080/callback",
scopes: ["mcp:read", "mcp:write"],
initial_token: %{
"access_token" => "...",
"refresh_token" => "...",
"expires_in" => 3600
}
}
)The client will automatically:
- Add Authorization headers to all requests
- Refresh tokens before expiration
- Handle 401/403 responses appropriately
Grant Types
OAuth specifies different flows for different use cases:
Authorization Code - User-based interactions where the client acts on behalf of a human
- Example: Agent calls MCP tool implemented by a SaaS system
- Requires user consent and PKCE for security
Client Credentials - Application-to-application communication (no user)
- Example: Agent calls secure MCP tool to check inventory
- Uses client ID/secret for authentication
Security Considerations
- All authorization endpoints MUST use HTTPS (except localhost for development)
- PKCE is REQUIRED for all authorization code flows
- Redirect URIs MUST be validated to prevent open redirect attacks
- Client secrets MUST be stored securely
- Access tokens MUST NOT be included in URI query strings
- Token expiration and rotation SHOULD be implemented
Error Handling
Common error responses:
401 Unauthorized- Authorization required or token invalid403 Forbidden- Invalid scopes or insufficient permissions400 Bad Request- Malformed authorization request
MCP Specification
This module implements the MCP Authorization specification (2025-06-18 revision). See MCP Authorization docs for the complete specification.
Summary
Functions
Performs OAuth 2.1 client credentials flow.
Discovers server metadata from the authorization server.
Exchanges an authorization code for an access token using PKCE.
Generates PKCE code challenge parameters.
Starts an OAuth 2.1 authorization code flow with PKCE.
Makes a token request to the authorization server.
Validates an access token with the authorization server.
Verifies a PKCE code challenge.
Types
Functions
@spec client_credentials_flow(map()) :: {:ok, token_response()} | {:error, term()}
Performs OAuth 2.1 client credentials flow.
This function implements the client credentials grant type for application-to-application authentication where no user interaction is required.
Parameters
Map containing:
:client_id- OAuth client identifier:client_secret- Client secret:token_endpoint- Authorization server's token endpoint:scopes- List of requested scopes (optional):resource- Optional resource indicator(s) (string or list of strings) per RFC 8707
Return Value
Returns {:ok, token_response} with access token information.
Example
{:ok, tokens} = ExMCP.Authorization.client_credentials_flow(%{
client_id: "service-client",
client_secret: "client-secret",
token_endpoint: "https://auth.example.com/oauth/token",
scopes: ["mcp:admin"],
resource: "https://inventory.api.example.com"
})
access_token = tokens.access_token
@spec discover_server_metadata(String.t()) :: {:ok, server_metadata()} | {:error, term()}
Discovers server metadata from the authorization server.
This function implements RFC 8414 (OAuth 2.0 Authorization Server Metadata) to automatically discover authorization endpoints and capabilities.
Parameters
issuer_url- Base URL of the authorization server
Return Value
Returns {:ok, server_metadata} with discovered endpoints and capabilities:
:authorization_endpoint- Authorization endpoint URL:token_endpoint- Token endpoint URL:registration_endpoint- Dynamic registration endpoint (optional):scopes_supported- Supported scopes:response_types_supported- Supported response types:grant_types_supported- Supported grant types:code_challenge_methods_supported- Supported PKCE methods
Example
{:ok, metadata} = ExMCP.Authorization.discover_server_metadata(
"https://auth.example.com"
)
auth_endpoint = metadata.authorization_endpoint
token_endpoint = metadata.token_endpoint
@spec exchange_code_for_token(map()) :: {:ok, token_response()} | {:error, term()}
Exchanges an authorization code for an access token using PKCE.
This function completes the authorization code flow by exchanging the authorization code received from the callback for an access token.
Parameters
Map containing:
:code- Authorization code from callback:code_verifier- PKCE code verifier from authorization state:client_id- OAuth client identifier:redirect_uri- Same redirect URI used in authorization request:token_endpoint- Authorization server's token endpoint:client_secret- Client secret (optional, for confidential clients):resource- Optional resource indicator(s) (string or list of strings) per RFC 8707
Return Value
Returns {:ok, token_response} with:
:access_token- Bearer token for API requests:token_type- Usually "Bearer":expires_in- Token lifetime in seconds (optional):refresh_token- Token for refreshing access (optional):scope- Granted scopes (optional)
Example
{:ok, tokens} = ExMCP.Authorization.exchange_code_for_token(%{
code: "auth_code_from_callback",
code_verifier: state.code_verifier,
client_id: "my-mcp-client",
redirect_uri: "http://localhost:8080/callback",
token_endpoint: "https://auth.example.com/oauth/token",
resource: "https://mcp.example.com"
})
access_token = tokens.access_token
Generates PKCE code challenge parameters.
This function generates the PKCE (Proof Key for Code Exchange) parameters required for secure authorization code flows.
Return Value
Returns {:ok, code_verifier, code_challenge} where:
code_verifier- Random string stored securely by clientcode_challenge- SHA256 hash of code_verifier for authorization request
Example
{:ok, verifier, challenge} = ExMCP.Authorization.generate_pkce_challenge()
# Use challenge in authorization URL
# Store verifier securely for token exchange
@spec start_authorization_flow(authorization_config()) :: {:ok, String.t(), map()} | {:error, term()}
Starts an OAuth 2.1 authorization code flow with PKCE.
This function initiates the authorization code flow for user-based authentication. It generates secure PKCE parameters and returns the authorization URL that the user should visit to grant consent.
Parameters
config- Authorization configuration map containing::client_id- OAuth client identifier:redirect_uri- Callback URI after authorization:authorization_endpoint- Authorization server's authorize endpoint:scopes- List of requested scopes:resource- Optional resource indicator(s) (string or list of strings) per RFC 8707:additional_params- Optional additional query parameters
Return Value
Returns {:ok, authorization_url, state} where:
authorization_url- URL for user to visit and authorizestate- Map containing code_verifier and state parameter (store securely)
Security
- All endpoints must use HTTPS (except localhost for development)
- PKCE code verifier is generated securely and must be stored
- State parameter prevents CSRF attacks
Example
{:ok, auth_url, state} = ExMCP.Authorization.start_authorization_flow(%{
client_id: "my-mcp-client",
redirect_uri: "http://localhost:8080/callback",
authorization_endpoint: "https://auth.example.com/oauth/authorize",
scopes: ["mcp:read", "mcp:write"],
resource: "https://mcp.example.com"
})
# Store state securely (contains code_verifier)
# Redirect user to auth_url
Makes a token request to the authorization server.
Used internally by TokenManager for refresh operations.
Validates an access token with the authorization server.
This function uses OAuth 2.0 token introspection to validate whether an access token is still active and get information about it.
Parameters
token- Access token to validateintrospection_endpoint- Token introspection endpoint URL
Return Value
Returns {:ok, introspection_response} if token is active, or
{:error, :token_inactive} if token is inactive.
Example
case ExMCP.Authorization.validate_token(token, introspection_endpoint) do
{:ok, info} ->
# Token is valid, info contains claims
:ok
{:error, :token_inactive} ->
# Token expired or revoked
:error
end
Verifies a PKCE code challenge.
This function verifies that a code verifier matches the expected code challenge. Used by authorization servers to validate PKCE flows.
Parameters
code_verifier- The code verifier from token exchangeexpected_challenge- The code challenge from authorization request
Return Value
Returns :ok if verification succeeds, {:error, reason} otherwise.
Example
case ExMCP.Authorization.verify_pkce_challenge(verifier, challenge) do
:ok -> # Valid
{:error, _reason} -> # Invalid
end