# `ConduitMcp.Plugs.Auth`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/plugs/auth.ex#L1)

Authentication plug for MCP servers.

Provides flexible authentication strategies for protecting MCP endpoints.
Supports bearer tokens, API keys, custom verification functions, and more.

## Options

- `:enabled` - Enable/disable authentication (default: `true`)
- `:strategy` - Authentication strategy: `:bearer_token`, `:api_key`, `:custom`, or `:function`
- `:verify` - Verification function/MFA. Signature: `(credential :: String.t()) -> {:ok, user} | {:error, reason}`

  > #### The returned reason must not embed the credential {: .warning}
  >
  > A failure reason is written to the log (clamped and stripped of control
  > characters via `ConduitMcp.Reflect`). Telemetry deliberately reports the
  > coarse atom `:invalid_credential` instead, because metadata is shipped
  > verbatim to metrics backends. Return `{:error, :not_found}`, not
  > `{:error, "no user for token abc123"}`.
- `:token` - Static token for `:bearer_token` strategy (simple auth). Must be
  a non-empty string: `init/1` raises otherwise, because an empty secret
  would match an empty `Authorization: Bearer ` header.
- `:api_key` - Static API key for `:api_key` strategy. Must be a non-empty
  string, for the same reason.
- `:header` - Header name for `:api_key` strategy (default: `"x-api-key"`)
- `:assign_as` - Key to assign authenticated user in conn.assigns (default: `:current_user`)
- `:principal_id` - Explicit stable identity for a static shared secret:
  requires `:token` (`:bearer_token` strategy) or `:api_key` (`:api_key`
  strategy). A shared static credential identifies exactly one principal;
  without this the id is a stable digest of the credential.
  Setting it anywhere else — `:function` / `:custom`, or a `:verify`
  function under `:bearer_token` / `:api_key` — raises `ArgumentError` at
  `init/1`: one fixed id would merge every user the verifier tells apart
  into a single principal. Return the identity from `:verify` instead.
  See `ConduitMcp.Principal` for every id format.

## The authenticated principal

On success this plug assigns:

  * `conn.assigns[:current_user]` (or `:assign_as`) — whatever your
    `:verify` function returned. Unchanged, and shaped however you like.
  * `conn.assigns[:mcp_principal]` — the canonical
    `ConduitMcp.Principal`, carrying a *stable scalar* `:id`. Task
    ownership and per-user rate limiting read this, never `:current_user`.

Read it with `ConduitMcp.Principal.get/1` / `ConduitMcp.Principal.id/1`.

## Examples

### Disabled (Development)

    plug ConduitMcp.Plugs.Auth, enabled: false

### Static Bearer Token

    plug ConduitMcp.Plugs.Auth,
      strategy: :bearer_token,
      token: "my-secret-token"

### Static API Key

    plug ConduitMcp.Plugs.Auth,
      strategy: :api_key,
      api_key: "secret-key-123",
      header: "x-api-key"

### Custom Function (Anonymous)

    plug ConduitMcp.Plugs.Auth,
      strategy: :function,
      verify: fn token ->
        if MyApp.Auth.valid_token?(token) do
          {:ok, MyApp.Auth.get_user_by_token(token)}
        else
          {:error, "Invalid token"}
        end
      end

### Custom Function (MFA)

    plug ConduitMcp.Plugs.Auth,
      strategy: :function,
      verify: {MyApp.Auth, :verify_token, []}  # Will call MyApp.Auth.verify_token(token)

### Database Token Lookup

    plug ConduitMcp.Plugs.Auth,
      strategy: :function,
      verify: fn token ->
        case MyApp.Repo.get_by(ApiToken, token: token) do
          %ApiToken{user_id: user_id} ->
            user = MyApp.Repo.get!(User, user_id)
            {:ok, user}
          nil ->
            {:error, "Invalid token"}
        end
      end

### JWT Verification

    plug ConduitMcp.Plugs.Auth,
      strategy: :function,
      verify: fn token ->
        case MyApp.JWT.verify_and_validate(token) do
          {:ok, claims} ->
            user = MyApp.Accounts.get_user!(claims["sub"])
            {:ok, user}
          {:error, _reason} ->
            {:error, "Invalid JWT"}
        end
      end

### OAuth2 Integration

    plug ConduitMcp.Plugs.Auth,
      strategy: :function,
      verify: {MyApp.OAuth, :verify_token, []},
      assign_as: :oauth_user

---

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