# `Sigra.JWT`
[🔗](https://github.com/sztheory/sigra/blob/v1.20.0/lib/sigra/jwt.ex#L1)

JWT access token generation, verification, and refresh token management.

Provides the opt-in JWT authentication path for Sigra. All functions guard
against Joken absence at runtime -- if Joken is not loaded, a clear error
is raised with installation instructions.

## Access Tokens

JWT access tokens contain standard claims (`sub`, `iat`, `exp`, `jti`, `iss`)
plus Sigra-specific claims (`scopes`, `epoch`). Custom claims can be added
via the `Sigra.JWT.ClaimsBuilder` behaviour.

## Refresh Tokens

Refresh tokens are opaque, hashed tokens (not JWTs) stored in the database
with family-based reuse detection. See `Sigra.JWT.RefreshToken` for details.

When **`:audit_schema`** is set under **`:audit`**, **`refresh/3`** commits
**`user_tokens`** rotation (or reuse-driven family revocation) and the
matching **`api.jwt_refresh`** / **`api.jwt_refresh_reuse`** audit row in
**one** transaction. Hosts should rely on that path for durable audit and
**avoid** calling **`Sigra.APIToken.audit_jwt_refresh/2`** afterward, which
would risk **double-audit** rows.

## Epoch Check

Every `verify_access/2` call checks the `epoch` claim against the user's
`token_epoch` field. This catches password changes, account deletion, and
"sign out everywhere" operations with a single DB read per request.

## Configuration

JWT support must be explicitly enabled:

    config = Sigra.Config.new!(
      repo: MyApp.Repo,
      user_schema: MyApp.User,
      secret_key_base: "...",
      jwt: [
        enabled: true,
        algorithm: "HS256",
        issuer: "my_app",
        access_ttl: 900,
        refresh_ttl: 2_592_000,
        claims_builder: MyApp.JWTClaimsBuilder
      ]
    )

# `generate_tokens`

```elixir
@spec generate_tokens(Sigra.Config.t(), struct(), [String.t()], keyword()) ::
  {:ok, map()} | {:error, term()}
```

Generates a JWT access token and optionally a refresh token.

Returns `{:ok, %{access_token: jwt, refresh_token: opaque, expires_in: ttl}}`
on success. If refresh tokens are disabled, `refresh_token` will be `nil`.

## Options

- `:user_token_schema` - Required when refresh is enabled. The Ecto schema
  module for user tokens.

## Examples

    {:ok, tokens} = Sigra.JWT.generate_tokens(config, user, ["read:users"])
    tokens.access_token  # => "eyJ..."
    tokens.refresh_token # => "abc123..."
    tokens.expires_in    # => 900

# `refresh`

```elixir
@spec refresh(Sigra.Config.t(), String.t(), keyword()) ::
  {:ok, map()}
  | {:error,
     :invalid_token | :token_expired | :reuse_detected | :jwt_refresh_aborted}
```

Refreshes an access token using a refresh token.

Rotates the refresh token (old token is superseded, new one created in the
same family) and generates a new access token with the same scopes.

## Options

- `:user_token_schema` - Required. The Ecto schema module for user tokens.

## Error Returns

- `{:error, :invalid_token}` - Refresh token not found
- `{:error, :token_expired}` - Refresh token has expired
- `{:error, :reuse_detected}` - Superseded token reused; entire family revoked
  (only after reuse handling **commits** successfully when auditing is on)
- `{:error, :jwt_refresh_aborted}` - When **`:audit_schema`** is set, refresh
  token rotation (or reuse revocation) and audit could not commit together
  (audit insert failure, constraint violation, etc.). This is an intentional
  exception to **D-AUD-06** “audit-only may return `:ok` on insert failure”:
  co-fated refresh rolls back persistence and surfaces this atom instead of
  returning new tokens without a matching audit row.

# `revoke_all_refresh`

```elixir
@spec revoke_all_refresh(Sigra.Config.t(), term(), keyword()) ::
  {:ok, non_neg_integer()}
```

Revokes all refresh tokens for a user.

Called during password change to invalidate all existing refresh tokens.

## Options

- `:user_token_schema` - Required. The Ecto schema module for user tokens.

# `revoke_refresh`

```elixir
@spec revoke_refresh(Sigra.Config.t(), String.t(), keyword()) ::
  :ok | {:error, :invalid_token}
```

Revokes a specific refresh token.

## Options

- `:user_token_schema` - Required. The Ecto schema module for user tokens.

# `verify_access`

```elixir
@spec verify_access(Sigra.Config.t(), String.t()) ::
  {:ok, map()} | {:error, :invalid_token | :token_expired | :epoch_mismatch}
```

Verifies a JWT access token.

Checks signature validity, expiration, and (if enabled) the epoch claim
against the user's current `token_epoch` value.

Returns `{:ok, claims}` on success.

## Error Returns

- `{:error, :invalid_token}` - Signature invalid or token malformed
- `{:error, :token_expired}` - Token has expired
- `{:error, :epoch_mismatch}` - User's token_epoch doesn't match claim

---

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