Sigra.JWT (Sigra v0.2.4)

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.

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}

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

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