Sigra.JWT (Sigra v1.20.0)

Copy Markdown View Source

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
  ]
)

Summary

Functions

Generates a JWT access token and optionally a refresh token.

Refreshes an access token using a refresh token.

Revokes all refresh tokens for a user.

Revokes a specific refresh token.

Verifies a JWT access token.

Functions

generate_tokens(config, user, scopes, opts \\ [])

@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(config, raw_refresh_token, opts \\ [])

@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(config, user_id, opts \\ [])

@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(config, raw_refresh_token, opts \\ [])

@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(config, jwt_string)

@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