Urchin.Auth.Claims (Urchin v0.4.0)

Copy Markdown View Source

Normalized claims for a validated OAuth 2.1 access token.

An Urchin.Auth.Authorizer returns one of these from authorize/3. The transport surfaces it to handlers as ctx.auth (see Urchin.Context), so a handler can make per-tool authorization decisions:

def call_tool("delete", _args, ctx) do
  if Urchin.Auth.Claims.has_scope?(ctx.auth, "files:write") do
    # ...
  else
    {:error, "files:write scope required"}
  end
end

from_map/1 converts a decoded JWT payload or an RFC 7662 introspection response into this struct, normalizing the OAuth/JWT field names (sub, aud, scope, exp, ...).

Summary

Functions

Returns true when any token audience covers the configured resource URI.

Builds a Claims struct from a decoded token payload (string- or atom-keyed map).

Returns true when the claims grant the given scope.

Returns true when the claims grant every scope in required.

Types

t()

@type t() :: %Urchin.Auth.Claims{
  audience: [String.t()],
  claims: map(),
  client_id: String.t() | nil,
  expires_at: integer() | nil,
  scopes: [String.t()],
  subject: String.t() | nil
}

Functions

covers_resource?(arg1, resource)

@spec covers_resource?(t() | nil, String.t() | URI.t()) :: boolean()

Returns true when any token audience covers the configured resource URI.

The comparison follows the resource binding semantics Urchin used before the authorizer handoff: the audience must share the same scheme, host and effective port, and its path may be the resource path itself or a parent path. For example, an audience of https://mcp.example.com covers https://mcp.example.com/mcp, but https://mcp.example.com/other does not.

from_map(payload)

@spec from_map(map()) :: t()

Builds a Claims struct from a decoded token payload (string- or atom-keyed map).

Recognized fields: sub, client_id/azp, exp, aud (string or list), scope (space-delimited string) and/or scp/scopes (string or list). JWT and RFC 7662 payloads are string-keyed, but an authorizer that hand-builds an atom-keyed map is also accepted. The full payload is preserved under :claims for custom checks.

has_scope?(arg1, scope)

@spec has_scope?(t() | nil, String.t()) :: boolean()

Returns true when the claims grant the given scope.

has_scopes?(arg1, required)

@spec has_scopes?(t() | nil, [String.t()]) :: boolean()

Returns true when the claims grant every scope in required.