AshAuthentication.Oauth2Server.Token (ash_authentication_oauth2_server v0.1.0)

Copy Markdown View Source

Protocol-pure logic for the /oauth/token endpoint.

Supports two grant types:

  • authorization_code — with PKCE verification, redirect/resource binding checks, and one-shot consumption of the code.
  • refresh_token — with rotation and reuse detection per OAuth 2.1 §4.3.1. A second use of an already-rotated refresh token revokes the entire descendant chain.

All functions return tagged tuples; controllers translate them to HTTP.

Summary

Types

Result of a successful grant — the bundle returned to the client.

Functions

Exchange an authorization code (with PKCE verifier) for an access + refresh token pair. Consumes the code atomically; a second call with the same code returns {:error, :reuse}.

Exchange a refresh token for a new access + refresh pair. Implements rotation + reuse detection (OAuth 2.1 §4.3.1): a second use of an already-rotated refresh token returns {:error, :reuse} and revokes the descendant chain.

Revoke a token per RFC 7009. Always returns :ok regardless of whether the token existed, was already revoked, or belonged to a different client — the RFC requires the endpoint not to leak token state.

Types

token_response()

@type token_response() :: %{
  access_token: String.t(),
  token_type: String.t(),
  expires_in: pos_integer(),
  refresh_token: String.t(),
  scope: String.t()
}

Result of a successful grant — the bundle returned to the client.

Functions

exchange_authorization_code(server, params)

@spec exchange_authorization_code(server :: module(), params :: map()) ::
  {:ok, token_response()} | {:error, atom()}

Exchange an authorization code (with PKCE verifier) for an access + refresh token pair. Consumes the code atomically; a second call with the same code returns {:error, :reuse}.

exchange_refresh_token(server, params)

@spec exchange_refresh_token(server :: module(), params :: map()) ::
  {:ok, token_response()} | {:error, atom()}

Exchange a refresh token for a new access + refresh pair. Implements rotation + reuse detection (OAuth 2.1 §4.3.1): a second use of an already-rotated refresh token returns {:error, :reuse} and revokes the descendant chain.

The rotation is atomic at the data-layer level — every "is this refresh usable" check lives in the :rotate action's filter, so validate + rotate is one query in the happy path. On a 0-row result (race lost, invalid token, expired, etc.) we do a follow-up read to distinguish :reuse from the other failure modes.

revoke(server, arg2)

@spec revoke(server :: module(), params :: map()) :: :ok

Revoke a token per RFC 7009. Always returns :ok regardless of whether the token existed, was already revoked, or belonged to a different client — the RFC requires the endpoint not to leak token state.

Only refresh tokens are revocable here: access tokens are stateless JWTs. When a refresh token is revoked, the entire descendant chain (rotated-to successors) is also revoked, so a refresh that has been rotated through cannot resurrect the session.

The params map mirrors what RFC 7009 §2.1 sends to the endpoint:

  • "token" (required) — the raw token string the client wishes to revoke.
  • "client_id" (required) — the public client identifier.
  • "token_type_hint" (optional) — "refresh_token" or "access_token". Treated as a hint only; access-token revocation is a silent no-op.