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
endVerified 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
Callbacks
@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.