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

Mint and verify OAuth 2.1 access tokens.

Uses HS256 with a shared secret resolved through the
`AshAuthentication.Secret` behaviour.

## Why this exists alongside `AshAuthentication.Jwt`

Both modules wrap Joken. They are kept separate because:

  * **Audience binding (RFC 8707)** — every minted token carries an `aud`
    matching the configured `resource_url`, and `verify/2` rejects tokens
    whose `aud` doesn't match. `AshAuthentication.Jwt`'s `aud` is
    hardcoded to a version constraint and is not customizable per-token.
  * **Hot-path verify** — the resource server validates a token on every
    protected request. Verify here is signature + claims only, no user
    load. The bearer plug controls when the user record is fetched.
  * **Decoupling** — these tokens identify a user by their primary key
    (`sub`), not via an AshAuthentication strategy, so we don't require
    the user resource to declare `authentication.tokens.enabled? true`.

# `mint`

```elixir
@spec mint(
  server :: module(),
  keyword()
) :: {:ok, String.t(), map()} | {:error, term()}
```

Mint a new access token.

Required keys: `:sub`, `:client_id`, `:scope`.
Optional: `:ttl` (seconds, defaults to the server's `access_token_lifetime`).

# `verify`

```elixir
@spec verify(server :: module(), String.t()) :: {:ok, map()} | {:error, term()}
```

Verify a token's signature, issuer, audience, expiry, and not-before.

Per RFC 7519 §4.1.4 we allow a small leeway on `exp` and `nbf` (the
server-configured `clock_skew_seconds`, default 30s) so tokens minted
by an AS whose clock is slightly off from the resource server still
verify.

Returns `{:ok, claims}` on success or `{:error, reason}` on failure.

---

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