Documentation
¶
Overview ¶
Package jwt implements JWT (JSON Web Token) authentication for Aegis.
This plugin provides stateless token-based authentication using industry-standard JWT tokens with RSA signing. Features include:
- Token Generation: Access tokens (15min) and refresh tokens (7d)
- RSA Signing: RS256 algorithm with database-backed key storage
- Key Rotation: Automatic key rotation with configurable intervals
- JWKS Endpoint: Public key discovery for token verification
- Token Blacklist: Redis-backed token revocation
- Refresh Flow: Secure token refresh without re-authentication
JWT vs Session Tokens:
Session Tokens (core): - Stateful: Requires database lookup on every request - Revocable: Can be deleted from database immediately - Simple: Just random strings, no cryptography JWT Tokens (this plugin): - Stateless: Can be verified without database (using public key) - Self-contained: Includes user ID and expiry in token - Distributed: Multiple services can verify without shared database - Revocation: Requires blacklist (Redis) for immediate revocation
Architecture:
Token Pair: - Access Token: Short-lived (15min), used for API requests - Refresh Token: Long-lived (7d), used to get new access tokens Key Storage: - Private keys: Stored in database, used for signing - Public keys: Exposed via /jwt/.well-known/jwks.json (JWKS endpoint) - Key rotation: Old keys retained for token verification Token Flow: 1. User authenticates (email/password, OAuth, etc.) 2. POST /jwt/token → Get access + refresh token 3. Use access token in Authorization header 4. When access token expires, POST /jwt/refreshToken with refresh token 5. Get new access + refresh token pair
Use Cases:
- Microservices: Stateless authentication across services
- Mobile apps: Long-lived refresh tokens
- SPAs: JavaScript apps with token storage
- Third-party integrations: Standard JWT format
Security Considerations:
- Access tokens are short-lived (minimize exposure window)
- Refresh tokens are long-lived (balance UX vs security)
- Token blacklist requires Redis (for logout/revocation)
- HTTPS is REQUIRED (tokens are bearer credentials)
- Store tokens securely (httpOnly cookies, secure storage, not localStorage)
Example:
package main
import (
"context"
"github.com/theinventorylib/aegis"
"github.com/theinventorylib/aegis/plugins/jwt"
)
func main() {
a, _ := aegis.New(context.Background(), ...)
// Configure JWT plugin
jwtConfig := &jwt.Config{
Issuer: "myapp",
AccessTokenExpiry: 15 * time.Minute,
RefreshTokenExpiry: 7 * 24 * time.Hour,
KeyRotationInterval: 24 * time.Hour,
}
// Register JWT plugin
a.Use(context.Background(), jwt.New(jwtConfig, nil, plugins.DialectPostgres))
a.MountRoutes("/auth")
// Routes available:
// POST /auth/jwt/token
// POST /auth/jwt/getAccessToken
// POST /auth/jwt/refreshToken
// POST /auth/jwt/logout
// GET /auth/jwt/.well-known/jwks.json
}
Package jwt provides database migration management for the JWT plugin.
This file handles parsing and loading migration files from the embedded filesystem, supporting multiple SQL dialects (PostgreSQL, MySQL, SQLite).
Migration Versioning:
- Version 001+: Migrations from migrations/<dialect>/<version>_<description>.<up|down>.sql
File Naming Convention:
- Up migrations: 001_initial.up.sql
- Down migrations: 001_initial.down.sql
Directory Structure:
jwt/
migrations/
postgres/
002_altered.up.sql
002_altered.down.sql
mysql/
002_altered.up.sql
002_altered.down.sql
The migration system ensures the correct schema version is applied for each SQL dialect, supporting forward (up) and backward (down) migrations.
Index ¶
- Constants
- func GetMigrations(dialect plugins.Dialect) ([]plugins.Migration, error)
- func GetSchemaRequirements(dialect plugins.Dialect) []plugins.SchemaRequirement
- type Config
- type Handler
- type Plugin
- func (p *Plugin) BlacklistToken(tokenStr string) error
- func (p *Plugin) CleanupExpiredKeys(ctx context.Context) error
- func (p *Plugin) Dependencies() []plugins.Dependency
- func (p *Plugin) Description() string
- func (p *Plugin) GenerateTokenPair(userID string) (*jwttypes.TokenPair, error)
- func (p *Plugin) GetMigrations() []plugins.Migration
- func (p *Plugin) Init(ctx context.Context, aegis plugins.Aegis) error
- func (p *Plugin) Logout(tokenStr string) error
- func (p *Plugin) LogoutAllSessions(userID string) error
- func (p *Plugin) MountRoutes(r router.Router, basePath string)
- func (p *Plugin) Name() string
- func (p *Plugin) ProvidesAuthMethods() []string
- func (p *Plugin) RefreshTokens(refreshToken string) (*jwttypes.TokenPair, error)
- func (p *Plugin) RequiresTables() []string
- func (p *Plugin) RotateKeys(ctx context.Context) error
- func (p *Plugin) Shutdown(_ context.Context) error
- func (p *Plugin) StartKeyRotation(ctx context.Context)
- func (p *Plugin) ValidateToken(tokenStr string) (*jwttypes.Claims, error)
- func (p *Plugin) Version() string
- type RefreshTokenRequest
- type TokenRequest
Constants ¶
const ( // TokenTypeAccess identifies an access token in JWT claims. // Access tokens are short-lived and used for API requests. TokenTypeAccess = "access" // TokenTypeRefresh identifies a refresh token in JWT claims. // Refresh tokens are long-lived and used to obtain new access tokens. TokenTypeRefresh = "refresh" )
const ( // SchemaTokenRequest is the OpenAPI schema name for POST /getToken request body. // Currently unused as /getToken accepts no body (uses authenticated user from context). SchemaTokenRequest = "TokenRequest" // SchemaRefreshTokenRequest is the OpenAPI schema name for POST /refreshToken request. // Request Body: // { // "refresh_token": "eyJhbGc..." // } SchemaRefreshTokenRequest = "RefreshTokenRequest" // SchemaTokenPair is the OpenAPI schema name for token pair responses. // Response Body: // { // "access_token": "eyJhbGc...", // "access_expiry": "2024-01-01T12:15:00Z", // "refresh_token": "eyJhbGc...", // "refresh_expiry": "2024-01-08T12:00:00Z" // } // // Used by: // - POST /getToken (success response) // - POST /getAccessToken (partial - access token only) // - POST /refreshToken (success response) SchemaTokenPair = "TokenPair" // SchemaAccessToken is the OpenAPI schema name for access token only responses. // Response Body: // { // "access_token": "eyJhbGc...", // "access_expiry": "2024-01-01T12:15:00Z" // } // // Used by: // - POST /getAccessToken (success response) SchemaAccessToken = "AccessToken" // SchemaJWKS is the OpenAPI schema name for JWKS responses. // Response Body: // { // "keys": [ // { // "kty": "RSA", // "use": "sig", // "kid": "access-1234567890", // "n": "...", // "e": "AQAB" // } // ] // } // // Used by: // - GET /.well-known/jwks.json // - GET /jwks SchemaJWKS = "JWKS" )
Schema names for OpenAPI specification generation.
These constants define the OpenAPI schema names for JWT request and response types. They are used in route metadata to generate accurate API documentation with typed request/response examples.
OpenAPI Integration: When routes are registered with schema metadata, the OpenAPI plugin uses these constants to link HTTP endpoints to their typed request/response structures.
Usage in Route Metadata:
route := core.Route{
Path: "/getToken",
Handler: handler.HandleGetToken,
Metadata: map[string]any{
"openapi": map[string]any{
"summary": "Generate JWT tokens",
"responses": map[string]any{
"200": map[string]any{
"description": "Token pair generated",
"schema": jwt.SchemaTokenPair,
},
},
},
},
}
Generated OpenAPI: The plugin converts these schema references into OpenAPI 3.0 schema definitions with JSON examples, field types, and validation rules.
Variables ¶
This section is empty.
Functions ¶
func GetMigrations ¶
GetMigrations returns all database migrations for the specified SQL dialect.
This function gets all migrations to produce a complete, ordered list of migrations.
Version Numbering:
- Version 001+: Additional migrations from migrations/<dialect>/ directory
File Naming:
- Format: <version>_<description>.<type>.sql
- Example: 001_initial.up.sql
- Type: "up" (apply) or "down" (rollback)
Migration Loading:
- Scan migrations/<dialect>/ for files
- Parse version number and type (up/down) from filename
- Group up/down migrations by version
- Sort by version number ascending
- Return ordered migration list
Parameters:
- dialect: SQL dialect (DialectPostgres, DialectMySQL, DialectSQLite)
Returns:
- []plugins.Migration: Ordered list of migrations (version ASC)
- error: File read error, invalid filename, or unsupported dialect
Example:
migrations, err := GetMigrations(plugins.DialectPostgres)
if err != nil {
log.Fatal(err)
}
for _, m := range migrations {
fmt.Printf("Version %d: %s\n", m.Version, m.Description)
// Execute m.Up SQL to apply migration
}
func GetSchemaRequirements ¶
func GetSchemaRequirements(dialect plugins.Dialect) []plugins.SchemaRequirement
GetSchemaRequirements returns schema validation requirements for the JWT plugin.
This function defines what database objects must exist for the plugin to function. The framework uses these requirements to validate the schema before plugin initialization.
Requirements:
- Table "jwks" must exist: Stores JSON Web Keys for JWT signing/verification
Validation Timing:
- Called during plugin.Init() after migrations are applied
- Plugin initialization fails if requirements are not met
- Prevents runtime errors from missing database objects
Parameters:
- dialect: SQL dialect (DialectPostgres, DialectMySQL, etc.)
Returns:
- []plugins.SchemaRequirement: List of validation checks to perform
- Empty slice if dialect is not supported
Example:
reqs := GetSchemaRequirements(plugins.DialectPostgres)
for _, req := range reqs {
if err := req.Validate(db); err != nil {
log.Fatal("Schema validation failed:", err)
}
}
Types ¶
type Config ¶
type Config struct {
// Issuer is the JWT "iss" claim identifying the token issuer.
// Should be your application name or domain (e.g., "myapp.com").
Issuer string
// AccessTokenExpiry is how long access tokens remain valid.
// Recommended: 15 minutes to 1 hour
// Shorter = more secure, longer = fewer refresh requests
AccessTokenExpiry time.Duration
// RefreshTokenExpiry is how long refresh tokens remain valid.
// Recommended: 7 days to 30 days
// Shorter = more secure, longer = better UX (less frequent logins)
RefreshTokenExpiry time.Duration
// KeyRotationInterval is how often to rotate signing keys.
// Recommended: 24 hours to 7 days
// More frequent rotation limits the impact of key compromise.
// Set to 0 to disable automatic rotation (manual rotation only).
KeyRotationInterval time.Duration
// KeySize is the RSA key size in bits (2048, 3072, or 4096).
// Only used for RSA algorithm.
// Recommended: 2048 (good balance of security and performance)
// 4096 provides higher security but slower signing/verification.
KeySize int
// KeyAlgorithm indicates the signing algorithm.
// Currently supported: "RSA" (uses RS256)
// Future: ECDSA (ES256), HMAC (HS256) for symmetric keys
KeyAlgorithm string
// KeyRetention is how long to keep rotated keys in storage.
// MUST be greater than the maximum token lifetime (RefreshTokenExpiry)
// to ensure old tokens can still be verified.
// Recommended: 30 days (covers refresh token lifetime + buffer)
KeyRetention time.Duration
}
Config holds JWT plugin configuration.
All durations should be balanced for security vs user experience:
- Shorter access tokens: More secure (tokens expire quickly)
- Longer refresh tokens: Better UX (less frequent re-authentication)
- Frequent key rotation: More secure (limits key exposure)
- Longer key retention: Required for old token validation
func DefaultConfig ¶
func DefaultConfig() *Config
DefaultConfig returns production-ready JWT configuration with security best practices.
Default values:
- Issuer: "aegis"
- Access token: 15 minutes (short-lived for security)
- Refresh token: 7 days (balance between UX and security)
- Key rotation: 24 hours (daily rotation limits key exposure)
- Key size: 2048 bits (industry standard RSA key size)
- Algorithm: RSA/RS256 (asymmetric signing)
- Key retention: 30 days (covers refresh token lifetime)
Customize for your use case:
config := jwt.DefaultConfig() config.Issuer = "myapp.com" config.AccessTokenExpiry = 1 * time.Hour // Longer for internal APIs config.RefreshTokenExpiry = 30 * 24 * time.Hour // 30 days for mobile apps
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler manages HTTP handlers for the JWT plugin.
All handlers have been made private (lowercase) to encourage programmatic use of the underlying Plugin methods. This struct serves as a mounting point for the router.
func NewHandler ¶
NewHandler creates a new JWT HTTP handler.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin represents the JWT authentication plugin.
This plugin manages the complete JWT token lifecycle:
- Token generation (access + refresh pairs)
- Token validation (signature + expiry)
- Token refresh (new tokens from refresh token)
- Key rotation (periodic key generation)
- Key storage (database-backed JWKS)
- Token blacklist (Redis-backed revocation)
Architecture:
Components: - store: Database storage for JWK keys - handler: HTTP handlers for token endpoints - config: Token expiry and key rotation settings - redisClient: Token blacklist storage - sessionService: Integration with core authentication Keys: - accessTokenPrivateKey: Signs access tokens - accessTokenPublicKey: Verifies access tokens (exposed in JWKS) - refreshTokenPrivateKey: Signs refresh tokens - refreshTokenPublicKey: Verifies refresh tokens (not exposed) Token Lifecycle: 1. Generate: CreateTokenPair() → JWT signed with private key 2. Use: Client includes token in Authorization header 3. Verify: ValidateToken() → Check signature with public key 4. Refresh: RefreshTokens() → Generate new pair from refresh token 5. Revoke: BlacklistToken() → Add to Redis blacklist
func New ¶
New creates a new JWT authentication plugin.
Parameters:
- config: JWT configuration (token expiry, key rotation, etc.) Pass nil to use DefaultConfig()
- store: Custom JWK storage implementation Pass nil to use default SQL storage (recommended)
- dialect: Database dialect (postgres, mysql, sqlite) Optional, defaults to postgres if not provided
Example:
// Use default configuration
jwtPlugin := jwt.New(nil, nil, plugins.DialectPostgres)
// Custom configuration
config := &jwt.Config{
Issuer: "myapp.com",
AccessTokenExpiry: 1 * time.Hour,
RefreshTokenExpiry: 30 * 24 * time.Hour,
KeyRotationInterval: 7 * 24 * time.Hour,
}
jwtPlugin := jwt.New(config, nil, plugins.DialectPostgres)
func (*Plugin) BlacklistToken ¶
BlacklistToken adds a token to the revocation list.
func (*Plugin) CleanupExpiredKeys ¶
CleanupExpiredKeys removes expired keys from the database.
func (*Plugin) Dependencies ¶
func (p *Plugin) Dependencies() []plugins.Dependency
Dependencies returns external package dependencies.
func (*Plugin) Description ¶
Description returns a human-readable description.
func (*Plugin) GenerateTokenPair ¶
GenerateTokenPair creates access and refresh tokens for a user
func (*Plugin) GetMigrations ¶
GetMigrations returns the plugin migrations
func (*Plugin) Init ¶
Init initializes the JWT plugin and integrates with core authentication.
Initialization steps:
- Initialize JWK storage (default SQL store if not provided)
- Get Redis client from SessionService (for token blacklist)
- Validate database schema (ensure jwk_keys table exists)
- Initialize signing keys (load from DB or generate new)
- Create HTTP handler for token endpoints
- Start automatic key rotation (if configured)
Database Schema Validation:
The plugin validates that the jwk_keys table exists with required columns:
- kid (key ID, primary key)
- key_data (JWK JSON)
- algorithm (RS256, etc.)
- use (sig for signing, enc for encryption)
- created_at, expires_at
Key Initialization:
On first run, generates RSA key pairs for:
- Access tokens (public key exposed in JWKS)
- Refresh tokens (public key NOT exposed)
On subsequent runs, loads existing keys from database.
Parameters:
- ctx: Context for initialization (can be canceled)
- aegis: Framework instance providing database, services, etc.
Returns:
- error: Schema validation errors, key generation errors
func (*Plugin) LogoutAllSessions ¶
LogoutAllSessions invalidates all tokens for a user.
func (*Plugin) MountRoutes ¶
MountRoutes registers HTTP routes for JWT endpoints with appropriate middleware.
func (*Plugin) ProvidesAuthMethods ¶
ProvidesAuthMethods returns authentication methods provided.
func (*Plugin) RefreshTokens ¶
RefreshTokens handles token refresh mechanism.
func (*Plugin) RequiresTables ¶
RequiresTables returns tables this plugin manages.
func (*Plugin) RotateKeys ¶
RotateKeys rotates both access and refresh keys
func (*Plugin) Shutdown ¶ added in v1.5.0
Shutdown stops the key-rotation goroutine and releases resources. Implements plugins.PluginShutdown.
func (*Plugin) StartKeyRotation ¶
StartKeyRotation begins periodic key rotation (only if Redis is configured).
func (*Plugin) ValidateToken ¶
ValidateToken checks the validity of a token.
type RefreshTokenRequest ¶
type RefreshTokenRequest struct {
RefreshToken string `json:"refresh_token"`
}
RefreshTokenRequest represents a token refresh request.
type TokenRequest ¶
type TokenRequest struct {
UserID string `json:"user_id"`
}
TokenRequest represents a request to get a JWT token.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package defaultstore implements the SQL-backed default store for the jwt plugin.
|
Package defaultstore implements the SQL-backed default store for the jwt plugin. |
|
internal
|
|
|
Package jwt defines the domain models and types used by the jwt plugin.
|
Package jwt defines the domain models and types used by the jwt plugin. |