# `AshAuthentication.Oauth2Server.Token`
[🔗](https://github.com/team-alembic/ash_authentication_oauth2_server/blob/v0.1.0/lib/ash_authentication/oauth2_server/token.ex#L5)

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.

# `token_response`

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

# `exchange_authorization_code`

```elixir
@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`

```elixir
@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`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
