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
@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
@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_schemais 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:okon insert failure”: co-fated refresh rolls back persistence and surfaces this atom instead of returning new tokens without a matching audit row.
@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.
@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.
@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