Anubis. Server. Authorization
(anubis_mcp v2.2.0)
Copy Markdown
OAuth 2.1 resource server authorization support.
Provides configuration, metadata building, and token validation primitives for securing MCP servers with bearer token authorization.
Standards Implemented
- RFC 6750 — Bearer Token Usage
- RFC 9728 — Protected Resource Metadata
- RFC 8707 — Resource Indicators (audience validation)
- RFC 7662 — Token Introspection
- RFC 7519 — JSON Web Token (JWT)
Configuration
use MyServer,
authorization: [
authorization_servers: ["https://auth.example.com"],
resource: "https://api.example.com",
realm: "mcp",
scopes_supported: ["tools:read", "tools:write"],
validator: {Anubis.Server.Authorization.JWTValidator,
jwks_uri: "https://auth.example.com/.well-known/jwks.json"}
]Claims map
After successful validation, a normalized claims map is stored in Context.auth:
%{
sub: "user-id",
aud: "https://api.example.com",
scope: "tools:read tools:write",
scopes: ["tools:read", "tools:write"],
exp: 1_234_567_890,
iat: 1_234_567_800,
client_id: "client-abc",
raw_claims: %{}
}
Summary
Types
Bounded authorization outcomes carried in telemetry :stop metadata.
Functions
Builds the RFC 9728 protected resource metadata map.
Builds the WWW-Authenticate header value for a 401 unauthorized response.
Normalizes raw claims (string-keyed map) into the canonical claims shape.
Maps an authorization or validator error reason to a bounded telemetry outcome.
Parses and validates the authorization configuration keyword list.
Validates that the token aud claim matches the server's canonical resource URI.
Validates that the token has not expired.
Validates that the claims contain all required scopes.
Returns the canonical /.well-known/oauth-protected-resource URL for a resource URI.
Types
@type outcome() ::
:ok
| :missing_token
| :invalid_token
| :invalid_issuer
| :invalid_audience
| :token_expired
| :invalid_expiry
| :validator_unavailable
| :other
Bounded authorization outcomes carried in telemetry :stop metadata.
Functions
Builds the RFC 9728 protected resource metadata map.
Examples
Authorization.build_resource_metadata(config)
# => %{
# "resource" => "https://api.example.com",
# "authorization_servers" => ["https://auth.example.com"],
# "scopes_supported" => ["tools:read"],
# "bearer_methods_supported" => ["header"]
# }
@spec build_www_authenticate( config(), :unauthorized | {:insufficient_scope, String.t()} ) :: String.t()
Builds the WWW-Authenticate header value for a 401 unauthorized response.
Includes resource_metadata URL per RFC 9728.
Examples
Authorization.build_www_authenticate(config, :unauthorized)
# => ~s(Bearer realm="mcp", resource_metadata="https://api.example.com/.well-known/oauth-protected-resource")
Normalizes raw claims (string-keyed map) into the canonical claims shape.
Parses the scope string into a scopes list for convenient membership checks.
If the raw claims already contain a scopes list (string- or atom-keyed), it is
preserved as-is so custom validators that emit pre-normalized data are honored.
Maps an authorization or validator error reason to a bounded telemetry outcome.
The Streamable HTTP plug wraps bearer authorization in a telemetry span whose
:stop metadata carries an outcome drawn from the bounded set in
outcome/0. Validator error reasons are free-form (atoms, strings, tuples),
so they are normalized here; raw reasons, tokens, and claims never reach
telemetry metadata.
Validator failures to reach or parse the token source (JWKS and introspection
fetches) map to :validator_unavailable; token verification and issuer
failures map to their specific outcomes; anything unrecognized maps to
:other.
Examples
Authorization.outcome(:token_expired)
# => :token_expired
Authorization.outcome(:invalid_signature)
# => :invalid_token
Authorization.outcome({:jwks_request_failed, :nxdomain})
# => :validator_unavailable
Authorization.outcome("unrecognized")
# => :other
Parses and validates the authorization configuration keyword list.
Raises ArgumentError if required fields are missing or invalid.
Examples
config = Authorization.parse_config!(
authorization_servers: ["https://auth.example.com"],
resource: "https://api.example.com",
validator: {MyValidator, []}
)
Validates that the token aud claim matches the server's canonical resource URI.
Returns :ok when the audience matches, {:error, :invalid_audience} otherwise.
Examples
Authorization.validate_audience(%{aud: "https://api.example.com"}, config)
# => :ok
Authorization.validate_audience(%{aud: "https://other.example.com"}, config)
# => {:error, :invalid_audience}
@spec validate_expiry(claims()) :: :ok | {:error, :token_expired | :invalid_expiry}
Validates that the token has not expired.
Compares exp against the current Unix timestamp.
Returns :ok if not expired, {:error, :token_expired} otherwise.
Tokens without exp are treated as non-expiring.
Examples
Authorization.validate_expiry(%{exp: future_timestamp})
# => :ok
@spec validate_scopes(claims(), [String.t()]) :: :ok | {:error, {:insufficient_scope, [String.t()]}}
Validates that the claims contain all required scopes.
Returns :ok when all required scopes are present in the claims,
{:error, {:insufficient_scope, required_scopes}} otherwise.
Examples
Authorization.validate_scopes(%{scopes: ["tools:read", "tools:write"]}, ["tools:read"])
# => :ok
Authorization.validate_scopes(%{scopes: ["tools:read"]}, ["tools:write"])
# => {:error, {:insufficient_scope, ["tools:write"]}}
Returns the canonical /.well-known/oauth-protected-resource URL for a resource URI.
Examples
Authorization.well_known_url("https://api.example.com")
# => "https://api.example.com/.well-known/oauth-protected-resource"