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.jsonfor 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
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable or disable the plugin |
algorithm | string | "eddsa" | Signing algorithm: eddsa, rs256, ps256, es256, es512 |
key_rotation_interval | duration | "720h" | How often to rotate signing keys (30 days) |
key_rotation_grace_period | duration | "1h" | Grace period for old key validity after rotation |
expires_in | duration | "15m" | Access token TTL |
refresh_expires_in | duration | "168h" | Refresh token TTL (7 days) |
jwks_cache_ttl | duration | "24h" | JWKS cache TTL |
refresh_grace_period | duration | "10s" | Grace period for refresh token reuse detection |
API Reference
| Method | Path | Description |
|---|---|---|
GET | /.well-known/jwks.json | Public JWKS endpoint for token verification |
POST | /token/refresh | Exchange refresh token for new token pair |
Database Schema
Table: jwt_keys
| Field | Type | Key | Description |
|---|---|---|---|
id | string | PK | Unique identifier for the key |
key_id | string | - | JWKS key ID (kid) |
public_key | string | - | PEM-encoded public key |
private_key | string | - | PEM-encoded private key |
algorithm | string | - | Signing algorithm used |
created_at | timestamp | - | Key creation time |
expires_at | timestamp | - | Key expiration time (after grace period) |
is_active | boolean | - | Whether this key is currently active for signing |
Table: jwt_refresh_tokens
| Field | Type | Key | Description |
|---|---|---|---|
id | string | PK | Unique identifier for the refresh token |
user_id | string | FK | Reference to the user |
session_id | string | FK | Reference to the session |
token | string | - | Hashed refresh token |
expires_at | timestamp | - | Token expiration time |
revoked_at | timestamp | - | When the token was revoked (if applicable) |
created_at | timestamp | - | 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
| Algorithm | Type | Recommended Use |
|---|---|---|
| EdDSA | Edwards-curve Digital Signature | Default — Fast, secure, modern |
| RS256 | RSA with SHA-256 | Legacy compatibility |
| PS256 | RSA-PSS with SHA-256 | Enhanced RSA security |
| ES256 | ECDSA with P-256 | Mobile/IoT constrained environments |
| ES512 | ECDSA with P-521 | Maximum security requirements |
| ECDH-ES | Elliptic Curve Diffie-Hellman | Key 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_intervalto 30 days or less for production environments - Short Access Token TTL — Keep
expires_inshort (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_ttlto 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_periodandrefresh_grace_periodbased on your token distribution needs
