# `ExMCP.Authorization.OAuthFlow`
[🔗](https://github.com/azmaveth/ex_mcp/blob/v1.0.0/lib/ex_mcp/authorization/oauth_flow.ex#L1)

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

# `auth_params`

```elixir
@type auth_params() :: %{
  optional(:issuer) =&gt; String.t(),
  optional(:require_issuer) =&gt; boolean(),
  optional(:resource) =&gt; String.t() | [String.t()],
  optional(:additional_params) =&gt; map(),
  client_id: String.t(),
  redirect_uri: String.t(),
  authorization_endpoint: String.t(),
  scopes: [String.t()]
}
```

# `client_credentials_params`

```elixir
@type client_credentials_params() :: %{
  optional(:scopes) =&gt; [String.t()],
  optional(:resource) =&gt; String.t() | [String.t()],
  client_id: String.t(),
  client_secret: String.t(),
  token_endpoint: String.t()
}
```

# `jwt_credentials_params`

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

# `token_params`

```elixir
@type token_params() :: %{
  optional(:client_secret) =&gt; String.t(),
  optional(:resource) =&gt; String.t() | [String.t()],
  optional(:transaction_id) =&gt; String.t(),
  code: String.t(),
  code_verifier: String.t(),
  client_id: String.t(),
  redirect_uri: String.t(),
  token_endpoint: String.t()
}
```

# `token_response`

```elixir
@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
}
```

# `client_credentials_flow`

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

Performs OAuth 2.1 client credentials flow.

# `client_credentials_jwt_flow`

```elixir
@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`

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

Exchanges an authorization code for tokens.

Include the `transaction_id` returned by `start_authorization_flow/1` to bind
redemption to the validated callback and exact redirect URI. The transaction
is marked redeemed before the network request. If the token response is lost,
retry the complete authorization flow rather than reusing the code.

# `reauthorize_with_scopes`

```elixir
@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`

```elixir
@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`

```elixir
@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, transaction} = OAuthFlow.start_authorization_flow(%{
      client_id: "my-client",
      redirect_uri: "http://localhost:8080/callback",
      authorization_endpoint: "https://auth.example.com/oauth/authorize",
      issuer: "https://auth.example.com",
      scopes: ["mcp:read", "mcp:write"]
    })

The returned transaction contains the library-generated state, PKCE verifier,
issuer, exact redirect URI, and an opaque `transaction_id`. Keep it private,
pass it unchanged to `validate_authorization_response/2`, and pass either its
`transaction_id` or the transaction itself through the authorization facade
when exchanging the code.

State is always generated by ExMCP. A top-level `:state` is rejected, as are
reserved OAuth parameters inside `:additional_params`; callers cannot replace
the state, PKCE, redirect, resource, client, response-type, or scope fields.

# `validate_authorization_response`

```elixir
@spec validate_authorization_response(map(), map()) ::
  {:ok, String.t()} | {:error, term()}
```

Validates an authorization response against its recorded transaction.

Transactions returned by `start_authorization_flow/1` are consumed atomically:
exactly one concurrent callback can succeed. The `state` value must match.
When the authorization response includes the RFC 9207 `iss` parameter, it must
exactly equal the issuer recorded when the flow started. If the authorization
server advertised `authorization_response_iss_parameter_supported: true`, the
transaction requires `iss` to be present as well. Issuers are identifiers and
are deliberately not URL-normalized. Validation succeeds with the authorization
code only after all checks pass.

Caller-constructed transaction maps without a `transaction_id` retain the 1.x
validation behavior for compatibility, but cannot provide process-independent
replay protection. Use the transaction returned by `start_authorization_flow/1`
for all new code.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
