The error value type and the wire-rendering helpers for the authorization-server controllers and the protected-resource plugs.
This module is both:
a struct - the controllers build
%AttestoPhoenix.OAuthError{}withnew/2/new/3and thread it through theirwithchains as the{:error, error}term, then render it once at the boundary withrender/2; anda set of header helpers -
unauthorized/4,use_dpop_nonce/3,insufficient_scope/3,no_store/2, andwww_authenticate/3- used by the protected-resource plugs to emitWWW-Authenticatechallenges and cache-suppression headers directly.
It is the single place the library turns an internal error into the bytes a client receives. It covers four surfaces, each governed by a different RFC:
Token / endpoint errors (RFC 6749 §5.2) - the JSON body
{"error": <code>, "error_description": <text>}returned by the token, revocation, and registration endpoints. When the request attempted HTTPAuthorization-based client authentication and the status is 401, RFC 6749 §5.2 requires a matchingWWW-Authenticatechallenge, sorender/2re-derives it from the request rather than trusting the caller to remember.Protected-resource challenges (RFC 6750 §3 / RFC 9449 §7.1) - a
WWW-Authenticateresponse header naming theBearerorDPoPscheme and carrying theerror,error_description,scope, and (for DPoP)algsauth-params.DPoP nonce challenges (RFC 9449 §8 / §9) - the
use_dpop_nonceerror returned with a freshDPoP-Nonceresponse header, telling the client to retry the request carrying that nonce.Cache suppression (RFC 6749 §5.1) -
no_store/2marks a response uncacheable withCache-Control: no-storeandPragma: no-cache, mandatory on every response that carries a token, and applied to every error response here for defense in depth.
Every quoted auth-param value is escaped per the WWW-Authenticate
quoted-string grammar (RFC 9110 §11.2 / RFC 7235): a bare " or \
inside a value would otherwise let an attacker break out of the quotes
and inject additional challenge parameters.
Configuration callbacks
The transport details are policy a host may override. Each is read from
AttestoPhoenix.Config and falls back to the RFC-correct default
implemented here when the host does not set it:
:send_error-(conn, status, body_map -> conn). Serializes the RFC 6749 §5.2 envelope and sends the response. Default encodes JSON withapplication/jsonand halts.:no_store-(conn -> conn). Sets the RFC 6749 §5.1 cache headers. Default setsCache-Control: no-storeandPragma: no-cache.:www_authenticate-(conn, challenge_string -> conn). Writes the challenge header. Default sets thewww-authenticateresponse header.
The RFC semantics (which code maps to which status, which auth-params, which header) are owned by this module and are not overridable; only the serialization/transport is.
This module compiles only when Plug is available.
Summary
Types
The protected-resource authentication scheme a challenge names.
An OAuth 2.0 error value rendered to the RFC 6749 §5.2 envelope.
Functions
Respond 403 insufficient_scope naming the required scopes
(RFC 6750 §3.1).
Build an OAuth 2.0 error value (RFC 6749 §5.2).
Apply the RFC 6749 §5.1 cache-suppression headers to conn.
Render an %AttestoPhoenix.OAuthError{} to the RFC 6749 §5.2 wire format.
Respond 401 with a protected-resource WWW-Authenticate challenge for
scheme (RFC 6750 §3 / RFC 9449 §7.1).
Respond 401 use_dpop_nonce carrying a fresh DPoP-Nonce header
(RFC 9449 §8 / §9).
Set the WWW-Authenticate response header to challenge.
Types
@type scheme() :: :bearer | :dpop
The protected-resource authentication scheme a challenge names.
@type t() :: %AttestoPhoenix.OAuthError{ error: atom(), error_description: String.t() | nil, status: pos_integer() }
An OAuth 2.0 error value rendered to the RFC 6749 §5.2 envelope.
Functions
@spec insufficient_scope(Plug.Conn.t(), [String.t()], scheme()) :: Plug.Conn.t()
Respond 403 insufficient_scope naming the required scopes
(RFC 6750 §3.1).
The WWW-Authenticate challenge for scheme carries the error,
error_description, and the RFC 6750 §3.1 scope auth-param listing the
scopes the request would need. Applies the RFC 6749 §5.1 no-store headers.
Build an OAuth 2.0 error value (RFC 6749 §5.2).
code is the error code atom (e.g. :invalid_request, :invalid_client).
description is the human-readable error_description (or nil). The
HTTP status defaults from the RFC 6749 §5.2 mapping for code and can be
overridden with the :status option.
@spec no_store(Plug.Conn.t(), AttestoPhoenix.Config.t() | nil) :: Plug.Conn.t()
Apply the RFC 6749 §5.1 cache-suppression headers to conn.
Sets Cache-Control: no-store and Pragma: no-cache. Mandatory on every
response that carries an access or refresh token. Delegates to the host's
:no_store callback when configured.
@spec render(Plug.Conn.t(), t()) :: Plug.Conn.t()
Render an %AttestoPhoenix.OAuthError{} to the RFC 6749 §5.2 wire format.
Writes the JSON envelope {"error": code, "error_description": desc} with
the error's status, applies the RFC 6749 §5.1 no-store headers, and - when
the request attempted Authorization-based client authentication and the
status is 401 - adds the RFC 6749 §5.2 WWW-Authenticate: Basic
challenge. The Basic realm defaults to "OAuth" and may be overridden by
the :basic_realm config key.
@spec unauthorized(Plug.Conn.t(), scheme(), String.t(), keyword()) :: Plug.Conn.t()
Respond 401 with a protected-resource WWW-Authenticate challenge for
scheme (RFC 6750 §3 / RFC 9449 §7.1).
The challenge carries error (an OAuth error code string) and, when
supplied, the optional auth-params. Options:
:description- theerror_descriptionauth-param.:scope- a space-delimited scope string for thescopeauth-param.:algs- a space-delimited list of acceptable DPoP signing algorithms for the RFC 9449 §5.1algsauth-param (:dpopscheme).:dpop_nonce- sets the RFC 9449 §8DPoP-Nonceresponse header.
Sets the status, the challenge header, any DPoP nonce header, the RFC 6749 §5.1 no-store headers, and writes the RFC 6749 §5.2 body.
@spec use_dpop_nonce(Plug.Conn.t(), String.t(), keyword()) :: Plug.Conn.t()
Respond 401 use_dpop_nonce carrying a fresh DPoP-Nonce header
(RFC 9449 §8 / §9).
The protected resource (or token endpoint) uses this to demand the client
retry the request including the server-issued nonce. Emits a DPoP
challenge whose error is use_dpop_nonce, sets the DPoP-Nonce
response header, and applies the RFC 6749 §5.1 no-store headers.
@spec www_authenticate(Plug.Conn.t(), AttestoPhoenix.Config.t() | nil, String.t()) :: Plug.Conn.t()
Set the WWW-Authenticate response header to challenge.
Delegates to the host's :www_authenticate callback when configured;
otherwise sets the www-authenticate header directly.