Audience-checking JWT verifier for an MCP mount.
The one thing Noizu.MCP.Auth.CompoundJWTVerifier does not do is bind the
token to this resource. This verifier does, and that binding is the point:
a token minted for https://host/mcp is rejected at https://host/mcp/learning
and vice versa, so a mount cannot be used as a confused deputy for its
neighbour.
forward "/mcp/learning", Noizu.MCP.Transport.StreamableHTTP.Plug,
server: MyApp.Learning.MCP,
auth: [
verifier: {Noizu.MCP.Auth.JWTVerifier, [
resource: "https://app.example.com/mcp/learning",
issuer: "https://app.example.com",
secret: {MyApp.MCPAuth, :secret},
algorithms: ["HS256"],
scopes: ["mcp"]
]},
resource_metadata: :derive
]Options
:resource(required) — the canonical resource URI of this mount. The token'saudmust match it exactly (see below).:secret— HMAC secret: a binary,{module, function}, or a 0-arity fun resolved per request (so a release reads it from runtime config, not from a compile-time literal).:jwk— an asymmetric key, as aJOSE.JWKor a JWK map. Use instead of:secretfor RS256/ES256.:algorithms— allowed JWS algorithms, default["HS256"]. Always read from config, never from the token header — that is what makesalg: "none"and HS/RS confusion non-issues here.:issuer— requiredissclaim, when set.:scopes— scopes every token must carry, else{:error, :insufficient_scope, %{"scope" => ...}}(→ 403 step-up).:leeway— clock skew allowance in seconds forexp/nbf, default0.:subject_required— reject a token with nosub, defaulttrue. An MCP tool handler that authorizes onctx.assigns.auth_claims["sub"]silently authorizes nobody whensubis missing.
Audience matching
aud must be either the configured resource as a string, or a single
element list holding it. A multi-audience token is rejected: it is valid at
more than one resource, which is exactly the property audience binding exists
to remove. Our own authorization server mints single-audience tokens, so this
costs nothing and closes the case where an upstream IdP is talked into adding
an audience.
Summary
Functions
The scopes a token carries, as a list. Handy in tool handlers reading
ctx.assigns.auth_claims.