AttestoPhoenix.OAuthError (AttestoPhoenix v0.6.1)

Copy Markdown View Source

The error value type and the wire-rendering helpers for the authorization-server controllers and the protected-resource plugs.

This module is both:

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 HTTP Authorization-based client authentication and the status is 401, RFC 6749 §5.2 requires a matching WWW-Authenticate challenge, so render/2 re-derives it from the request rather than trusting the caller to remember.

  • Protected-resource challenges (RFC 6750 §3 / RFC 9449 §7.1) - a WWW-Authenticate response header naming the Bearer or DPoP scheme and carrying the error, error_description, scope, and (for DPoP) algs auth-params.

  • DPoP nonce challenges (RFC 9449 §8 / §9) - the use_dpop_nonce error returned with a fresh DPoP-Nonce response header, telling the client to retry the request carrying that nonce.

  • Cache suppression (RFC 6749 §5.1) - no_store/2 marks a response uncacheable with Cache-Control: no-store and Pragma: 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 with application/json and halts.
  • :no_store - (conn -> conn). Sets the RFC 6749 §5.1 cache headers. Default sets Cache-Control: no-store and Pragma: no-cache.
  • :www_authenticate - (conn, challenge_string -> conn). Writes the challenge header. Default sets the www-authenticate response 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.

t()

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

scheme()

@type scheme() :: :bearer | :dpop

The protected-resource authentication scheme a challenge names.

t()

@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

insufficient_scope(conn, required, scheme \\ :bearer)

@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.

new(code, description \\ nil, opts \\ [])

@spec new(atom(), String.t() | nil, keyword()) :: t()

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.

no_store(conn, config \\ nil)

@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.

render(conn, error)

@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.

unauthorized(conn, scheme, error, opts \\ [])

@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 - the error_description auth-param.
  • :scope - a space-delimited scope string for the scope auth-param.
  • :algs - a space-delimited list of acceptable DPoP signing algorithms for the RFC 9449 §5.1 algs auth-param (:dpop scheme).
  • :dpop_nonce - sets the RFC 9449 §8 DPoP-Nonce response 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.

use_dpop_nonce(conn, nonce, opts \\ [])

@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.

www_authenticate(conn, config \\ nil, challenge)

@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.