App Logo
Plugins

JWT Plugin

JWKS-based JWT authentication with automatic key rotation and refresh token support for SPAs and mobile applications

Overview

The JWT plugin provides JWKS-based JWT authentication with automatic key rotation and refresh token support. It is designed for SPAs and mobile applications, offering short-lived access tokens with long-lived refresh tokens, multiple signing algorithms, and public key distribution via a JWKS endpoint.

Features

  • Multiple Signing Algorithms — EdDSA (default), RS256, PS256, ES256, ES512, ECDH-ES
  • Automatic Key Rotation — Rotates signing keys on configurable intervals with grace periods
  • Token Pairs — Short-lived access tokens (15m default) with long-lived refresh tokens (7d default)
  • JWKS Endpoint — Public key distribution via /.well-known/jwks.json for token verification
  • Token Blacklisting — Revoke tokens immediately when needed (requires Secondary Storage plugin)
  • Refresh Token Rotation — New refresh token issued on each refresh for security
  • Grace Periods — Smooth transitions during key rotation and refresh token reuse detection

Configuration

Standalone Mode

[plugins.jwt]
enabled = true
algorithm = "eddsa"
key_rotation_interval = "720h"
key_rotation_grace_period = "1h"
expires_in = "15m"
refresh_expires_in = "168h"
jwks_cache_ttl = "24h"
refresh_grace_period = "10s"

Library Mode

import (
	jwtplugin "github.com/Authula/authula/plugins/jwt"
	jwtplugintypes "github.com/Authula/authula/plugins/jwt/types"
)

jwtplugin.New(jwtplugintypes.JWTPluginConfig{
	Enabled:                true,
	Algorithm:              jwtplugintypes.JWTAlgEdDSA,
	ExpiresIn:              15 * time.Minute,
	KeyRotationInterval:    720 * time.Hour,
	KeyRotationGracePeriod: time.Hour,
	RefreshExpiresIn:       168 * time.Hour,
	JWKSCacheTTL:           24 * time.Hour,
	RefreshGracePeriod:     10 * time.Second,
}),

Configuration Options

OptionTypeDefaultDescription
enabledbooleantrueEnable or disable the plugin
algorithmstring"eddsa"Signing algorithm: eddsa, rs256, ps256, es256, es512
key_rotation_intervalduration"720h"How often to rotate signing keys (30 days)
key_rotation_grace_periodduration"1h"Grace period for old key validity after rotation
expires_induration"15m"Access token TTL
refresh_expires_induration"168h"Refresh token TTL (7 days)
jwks_cache_ttlduration"24h"JWKS cache TTL
refresh_grace_periodduration"10s"Grace period for refresh token reuse detection

API Reference

MethodPathDescription
GET/.well-known/jwks.jsonPublic JWKS endpoint for token verification
POST/token/refreshExchange refresh token for new token pair

Database Schema

Table: jwt_keys

FieldTypeKeyDescription
idstringPKUnique identifier for the key
key_idstring-JWKS key ID (kid)
public_keystring-PEM-encoded public key
private_keystring-PEM-encoded private key
algorithmstring-Signing algorithm used
created_attimestamp-Key creation time
expires_attimestamp-Key expiration time (after grace period)
is_activeboolean-Whether this key is currently active for signing

Table: jwt_refresh_tokens

FieldTypeKeyDescription
idstringPKUnique identifier for the refresh token
user_idstringFKReference to the user
session_idstringFKReference to the session
tokenstring-Hashed refresh token
expires_attimestamp-Token expiration time
revoked_attimestamp-When the token was revoked (if applicable)
created_attimestamp-Record creation time

Migrations are automatically handled when the plugin is initialized.


Plugin Capabilities

The JWT plugin provides the following capabilities:

  • jwt.respond_json — Sends JWT tokens in JSON response after successful authentication

Token Lifecycle

┌─────────────┐     ┌──────────────┐     ┌──────────────┐
│   Sign In   │────▶│   Access     │────▶│   Refresh    │
│   Success   │     │   Token      │     │   Token      │
└─────────────┘     └──────┬───────┘     └──────┬───────┘
                           │                      │
                           ▼                      │
                    ┌──────────────┐              │
                    │   API Calls  │──────────────┘
                    │   (15 min)   │    When expired
                    └──────┬───────┘

              ┌────────────┼────────────┐
              ▼            ▼            ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │  Valid   │ │ Expired  │ │ Sign Out │
        │  Request │ │  Token   │ │  (Manual)│
        └────┬─────┘ └────┬─────┘ └────┬─────┘
             │            │            │
             ▼            ▼            ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ Continue │ │ Refresh  │ │  Revoke  │
        │          │ │ Endpoint │ │  Tokens  │
        └──────────┘ └────┬─────┘ └──────────┘


                   ┌──────────────┐
                   │  New Token   │
                   │    Pair      │
                   └──────────────┘

Key Rotation Lifecycle

┌─────────────┐     ┌──────────────┐     ┌──────────────┐
│   Key Gen   │────▶│   Active     │────▶│   Rotation   │
│   (Init)    │     │   Signing    │     │   (Interval) │
└─────────────┘     └──────┬───────┘     └──────┬───────┘
                           │                      │
                           │               ┌──────┴──────┐
                           │               ▼             ▼
                           │        ┌──────────┐  ┌──────────┐
                           │        │ New Key  │  │ Old Key  │
                           │        │ (Active) │  │ (Grace)  │
                           │        └────┬─────┘  └────┬─────┘
                           │             │             │
                           └─────────────┴─────────────┘


                                  ┌──────────────┐
                                  │   Old Key    │
                                  │  (Expired)   │
                                  └──────────────┘

How to Use

Enable JWT responses on authentication routes:

[[route_mappings]]
method = "POST"
path = "/auth/sign-in"
plugins = ["bearer.auth.optional", "jwt.respond_json"]

After successful authentication, the response will include:

{
  "access_token": "eyJhbGciOiJFZERTQSJ9...",
  "refresh_token": "eyJhbGciOiJFZERTQSJ9..."
}

Algorithm Support

AlgorithmTypeRecommended Use
EdDSAEdwards-curve Digital SignatureDefault — Fast, secure, modern
RS256RSA with SHA-256Legacy compatibility
PS256RSA-PSS with SHA-256Enhanced RSA security
ES256ECDSA with P-256Mobile/IoT constrained environments
ES512ECDSA with P-521Maximum security requirements
ECDH-ESElliptic Curve Diffie-HellmanKey exchange only, not for signing

Note: ECDH-ES is available for key exchange but cannot be used for JWT signing. May be removed in a future version.


Dependencies

  • Secondary Storage (optional) — Enables token blacklisting for immediate revocation

Client Plugin

If you're using the Authula SDK, add the plugin to the SDK like so:

import { createClient } from "authula";
import { JWTPlugin } from "authula/plugins";

export const authulaClient = createClient({
  url: "http://localhost:8080/auth",
  plugins: [
    // other plugins...
    new JWTPlugin(),
  ],
});

Security Recommendations

  • Use EdDSA by Default — EdDSA provides the best balance of security and performance for most use cases
  • Enable Key Rotation — Set key_rotation_interval to 30 days or less for production environments
  • Short Access Token TTL — Keep expires_in short (15 minutes or less) to minimize exposure if tokens are compromised
  • Refresh Token Rotation — Each refresh issues a new refresh token, invalidating the old one to detect replay attacks
  • Token Blacklisting — Use the Secondary Storage plugin to enable immediate token revocation on sign-out
  • Secure JWKS Caching — Set appropriate jwks_cache_ttl to balance performance and key rotation responsiveness
  • HTTPS Required — Always use HTTPS in production to protect tokens in transit
  • Grace Period Tuning — Adjust key_rotation_grace_period and refresh_grace_period based on your token distribution needs

On this page