ExMCP.Authorization.OAuthFlow (ex_mcp v0.10.0)

View Source

OAuth 2.1 flow implementations for MCP authorization.

This module handles the core OAuth flows:

  • Authorization Code Flow with PKCE
  • Client Credentials Flow
  • Token refresh flow

Summary

Functions

Performs OAuth 2.1 client credentials flow.

Performs OAuth 2.1 client credentials flow with JWT client authentication (private_key_jwt).

Exchanges an authorization code for tokens.

Initiates a full re-authorization flow with an expanded scope set.

Refreshes an access token using a refresh token.

Starts the OAuth 2.1 authorization code flow with PKCE.

Types

auth_params()

@type auth_params() :: %{
  optional(:state) => String.t(),
  optional(:resource) => String.t() | [String.t()],
  client_id: String.t(),
  redirect_uri: String.t(),
  authorization_endpoint: String.t(),
  scopes: [String.t()]
}

client_credentials_params()

@type client_credentials_params() :: %{
  optional(:scopes) => [String.t()],
  optional(:resource) => String.t() | [String.t()],
  client_id: String.t(),
  client_secret: String.t(),
  token_endpoint: String.t()
}

jwt_credentials_params()

@type jwt_credentials_params() :: %{
  optional(:scopes) => [String.t()],
  optional(:resource) => String.t() | [String.t()],
  optional(:alg) => String.t(),
  optional(:kid) => String.t(),
  client_id: String.t(),
  private_key: JOSE.JWK.t(),
  token_endpoint: String.t()
}

token_params()

@type token_params() :: %{
  optional(:client_secret) => String.t(),
  optional(:resource) => String.t() | [String.t()],
  code: String.t(),
  code_verifier: String.t(),
  client_id: String.t(),
  redirect_uri: String.t(),
  token_endpoint: String.t()
}

token_response()

@type token_response() :: %{
  access_token: String.t(),
  token_type: String.t(),
  expires_in: non_neg_integer() | nil,
  refresh_token: String.t() | nil,
  scope: String.t() | nil
}

Functions

client_credentials_flow(params)

@spec client_credentials_flow(client_credentials_params()) ::
  {:ok, token_response()} | {:error, term()}

Performs OAuth 2.1 client credentials flow.

client_credentials_jwt_flow(params)

@spec client_credentials_jwt_flow(jwt_credentials_params()) ::
  {:ok, token_response()} | {:error, term()}

Performs OAuth 2.1 client credentials flow with JWT client authentication (private_key_jwt).

Uses RFC 7523 Section 2.2 client assertions instead of a client secret.

exchange_code_for_token(params)

@spec exchange_code_for_token(token_params()) ::
  {:ok, token_response()} | {:error, term()}

Exchanges an authorization code for tokens.

reauthorize_with_scopes(params, additional_scopes)

@spec reauthorize_with_scopes(auth_params(), [String.t()]) ::
  {:ok, String.t(), map()} | {:error, term()}

Initiates a full re-authorization flow with an expanded scope set.

Used when a refresh token is not available or the server does not support scope upgrades via refresh. This starts a new authorization code flow with the combined current + additional scopes.

refresh_token(refresh_token, client_id, token_endpoint, opts \\ nil)

@spec refresh_token(String.t(), String.t(), String.t(), keyword() | String.t() | nil) ::
  {:ok, token_response()} | {:error, term()}

Refreshes an access token using a refresh token.

Options

  • client_secret - Client secret for confidential clients (default: nil)
  • scope - Space-separated scope string to request expanded scopes during refresh. Used for incremental scope upgrades (2025-11-25). If the authorization server supports it, the new token will have the expanded scope set.

start_authorization_flow(params)

@spec start_authorization_flow(auth_params()) ::
  {:ok, String.t(), map()} | {:error, term()}

Starts the OAuth 2.1 authorization code flow with PKCE.

Example

{:ok, auth_url, state} = OAuthFlow.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"]
})