Noizu.MCP.Auth.TokenVerifier behaviour (Noizu MCP v0.1.2)

Copy Markdown View Source

Server-side bearer-token verification for the Streamable HTTP transport.

MCP servers are OAuth 2.1 resource servers: they validate access tokens but never issue them. Implement this behaviour and pass it to the transport plug:

forward "/mcp", Noizu.MCP.Transport.StreamableHTTP.Plug,
  server: MyApp.MCP,
  auth: [
    verifier: {MyApp.TokenVerifier, []},
    resource_metadata: "https://api.example.com/.well-known/oauth-protected-resource"
  ]

defmodule MyApp.TokenVerifier do
  @behaviour Noizu.MCP.Auth.TokenVerifier

  @impl true
  def verify(token, _conn_info, _opts) do
    with {:ok, claims} <- MyApp.JWT.verify(token),
         # RFC 8707: the token must be audience-bound to THIS server.
         true <- claims["aud"] == "https://api.example.com/mcp" || {:error, :invalid_token} do
      {:ok, claims}
    end
  end
end

Verified claims are exposed to handlers as ctx.assigns.auth_claims (seeded at session initialize).

Summary

Callbacks

Verify a bearer token. Return {:ok, claims}, {:error, :invalid_token} (→ 401), or {:error, :insufficient_scope, meta} (→ 403 with an insufficient_scope challenge; meta may include "scope").

Types

conn_info()

@type conn_info() :: %{
  method: String.t(),
  peer: term(),
  headers: [{binary(), binary()}]
}

Callbacks

verify(token, conn_info, opts)

@callback verify(token :: String.t(), conn_info(), opts :: term()) ::
  {:ok, claims :: map()}
  | {:error, :invalid_token}
  | {:error, :insufficient_scope, meta :: map()}

Verify a bearer token. Return {:ok, claims}, {:error, :invalid_token} (→ 401), or {:error, :insufficient_scope, meta} (→ 403 with an insufficient_scope challenge; meta may include "scope").

Audience validation (RFC 8707) is the verifier's responsibility — reject tokens minted for other resources.