jwt

package
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 30, 2026 License: MIT Imports: 25 Imported by: 0

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

View Source
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"
)
View Source
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

func GetMigrations(dialect plugins.Dialect) ([]plugins.Migration, error)

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:

  1. Scan migrations/<dialect>/ for files
  2. Parse version number and type (up/down) from filename
  3. Group up/down migrations by version
  4. Sort by version number ascending
  5. 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

func NewHandler(plugin *Plugin) *Handler

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

func New(config *Config, store jwttypes.Store, dialect ...plugins.Dialect) *Plugin

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

func (p *Plugin) BlacklistToken(tokenStr string) error

BlacklistToken adds a token to the revocation list.

func (*Plugin) CleanupExpiredKeys

func (p *Plugin) CleanupExpiredKeys(ctx context.Context) error

CleanupExpiredKeys removes expired keys from the database.

func (*Plugin) Dependencies

func (p *Plugin) Dependencies() []plugins.Dependency

Dependencies returns external package dependencies.

func (*Plugin) Description

func (p *Plugin) Description() string

Description returns a human-readable description.

func (*Plugin) GenerateTokenPair

func (p *Plugin) GenerateTokenPair(userID string) (*jwttypes.TokenPair, error)

GenerateTokenPair creates access and refresh tokens for a user

func (*Plugin) GetMigrations

func (p *Plugin) GetMigrations() []plugins.Migration

GetMigrations returns the plugin migrations

func (*Plugin) Init

func (p *Plugin) Init(ctx context.Context, aegis plugins.Aegis) error

Init initializes the JWT plugin and integrates with core authentication.

Initialization steps:

  1. Initialize JWK storage (default SQL store if not provided)
  2. Get Redis client from SessionService (for token blacklist)
  3. Validate database schema (ensure jwk_keys table exists)
  4. Initialize signing keys (load from DB or generate new)
  5. Create HTTP handler for token endpoints
  6. 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) Logout

func (p *Plugin) Logout(tokenStr string) error

Logout is a programmatic alias for BlacklistToken.

func (*Plugin) LogoutAllSessions

func (p *Plugin) LogoutAllSessions(userID string) error

LogoutAllSessions invalidates all tokens for a user.

func (*Plugin) MountRoutes

func (p *Plugin) MountRoutes(r router.Router, basePath string)

MountRoutes registers HTTP routes for JWT endpoints with appropriate middleware.

func (*Plugin) Name

func (p *Plugin) Name() string

Name returns the plugin identifier.

func (*Plugin) ProvidesAuthMethods

func (p *Plugin) ProvidesAuthMethods() []string

ProvidesAuthMethods returns authentication methods provided.

func (*Plugin) RefreshTokens

func (p *Plugin) RefreshTokens(refreshToken string) (*jwttypes.TokenPair, error)

RefreshTokens handles token refresh mechanism.

func (*Plugin) RequiresTables

func (p *Plugin) RequiresTables() []string

RequiresTables returns tables this plugin manages.

func (*Plugin) RotateKeys

func (p *Plugin) RotateKeys(ctx context.Context) error

RotateKeys rotates both access and refresh keys

func (*Plugin) Shutdown added in v1.5.0

func (p *Plugin) Shutdown(_ context.Context) error

Shutdown stops the key-rotation goroutine and releases resources. Implements plugins.PluginShutdown.

func (*Plugin) StartKeyRotation

func (p *Plugin) StartKeyRotation(ctx context.Context)

StartKeyRotation begins periodic key rotation (only if Redis is configured).

func (*Plugin) ValidateToken

func (p *Plugin) ValidateToken(tokenStr string) (*jwttypes.Claims, error)

ValidateToken checks the validity of a token.

func (*Plugin) Version

func (p *Plugin) Version() string

Version returns the plugin version.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL