Accepts a raw API key presented as a bearer token.
Headless callers (a cron job, a CI step, a CLI that cannot open a browser)
have no way to run the OAuth dance. This verifier lets them present a
long-lived key in the same Authorization: Bearer header, so a mount can
serve both audiences — pair it with Noizu.MCP.Auth.ChainVerifier.
auth: [
verifier: {Noizu.MCP.Auth.ChainVerifier, [
verifiers: [
{Noizu.MCP.Auth.JWTVerifier, [resource: resource, secret: {MyApp, :secret}]},
{Noizu.MCP.Auth.ApiKeyVerifier, [
resource: resource,
prefix: "mcp_live_",
validator: {MyApp.MCPKeys, :verify}
]}
]
]}
]The key store belongs to the host — only it knows the table. :validator is
called with the presented key and must answer
{:ok, claims} | {:error, reason} | false | nil. It must compare
against a hash, not a plaintext column; Noizu.MCP.Auth.Server.Secret
provides token_hash/1 and a constant-time equal?/2 for that.
Options
:validator(required unless:keysis given) —{module, function},{module, function, extra_args}(the key is prepended), or a 1-arity fun.:keys— a static[{key, claims}]list, compared in constant time. For tests and single-operator deployments; a real key list belongs in a database, behind:validator.:resource— canonical resource URI of the mount. Stamped onto the claims asaudwhen the validator did not set one: an API key is bound to a resource by where it was configured, and downstream code that readsaudshould see the same shape either way.:prefix— required key prefix. A cheap non-secret shape check that keeps obviously-wrong credentials (an OAuth JWT, say) from reaching the database on every request.:scopes— scopes the resulting claims must carry, checked exactly asNoizu.MCP.Auth.JWTVerifierdoes.:default_claims— merged under whatever the validator returns (e.g.%{"scope" => "mcp"}).
Failure is always {:error, :invalid_token} — a caller cannot distinguish
"no such key" from "revoked key" from "wrong prefix".