aegis

package module
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Feb 23, 2026 License: MIT Imports: 12 Imported by: 0

README

Aegis

Aegis Logo

Aegis is a lightweight authentication framework for Go with a modular plugin architecture inspired by better-auth.

CI Go Reference


✨ Features

Core Authentication
  • Minimal Core Schema: Only 4 essential tables
  • Database Agnostic: Works with PostgreSQL, MySQL, SQLite
  • Session Management: Secure token-based sessions with refresh tokens and Redis caching
  • CSRF Protection: Built-in CSRF protection for web applications
  • Password Authentication: Argon2id hashing built into core (not a plugin)
  • Developer Friendly: No auto-migration magic, fully typed API
7 Official Plugins
  • Email - Email verification via OTP and email+password auth
  • SMS - Phone number verification via OTP
  • OAuth - Social login (Google, GitHub, and more)
  • JWT - Token generation, validation, and rotation
  • Admin - Administrative endpoints for user management
  • Organizations - Multi-tenant organization and team support
  • OpenAPI - Interactive API documentation with Scalar UI
Built-in Features
  • Bearer Auth - Token authentication via Authorization header (config option, auto-enabled in API mode)
CLI Tool
  • Migration Export: Export database migrations in multiple formats
  • Format Support: SQL, Goose, golang-migrate
  • Plugin Selection: Export core + specific plugins or all at once

🚀 Quick Install

go get github.com/theinventorylib/aegis

For the CLI tool:

go install github.com/theinventorylib/aegis/cmd/aegis@latest

🤝 Contributing

We welcome contributions! See .github/COMMIT_GUIDE.md for commit conventions and .github/RELEASE.md for release process.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package aegis provides the main authentication framework instance.

Aegis is a comprehensive authentication and authorization framework for Go web applications. It provides:

  • User authentication: Email/password, OAuth, JWT, SMS, email OTP
  • Session management: Token-based sessions with Redis caching
  • Password security: Argon2id hashing with OWASP-recommended parameters
  • Rate limiting: Sliding window rate limiting with brute force protection
  • Multi-tenancy: Organization-based isolation via plugins
  • Plugin system: Extensible architecture for custom authentication methods
  • Audit logging: Comprehensive security event tracking
  • Database support: PostgreSQL, MySQL, SQLite with migration tools

Architecture Modes:

  1. Web Mode (Default): - Best for: Server-Side Rendered apps (Templ, HTML templates), SPAs on same domain. - Auth: HTTP-Only Cookies (secure, max-age, same-site). - Security: CSRF Protection enabled (requires Master Secret). - Bearer: Disabled by default (enable with config.WithBearerAuth(true)).

  2. API Mode: - Best for: Mobile apps, CLI tools, Service-to-Service, standalone frontends. - Auth: "Authorization: Bearer <token>" header (auto-enabled). - Security: CSRF checks skipped (cookies not used for auth). - Enabled via: config.WithAPIOnlyMode(true).

  3. Dual Mode (Web + API): - Best for: Apps serving both browser clients and API consumers. - Auth: Both cookies and Bearer tokens accepted. - Security: CSRF Protection enabled for cookie-based requests. - Enabled via: config.WithBearerAuth(true) (without APIMode).

Quick Start:

package main

import (
	"context"
	"database/sql"
	"net/http"

	"github.com/theinventorylib/aegis"
	"github.com/theinventorylib/aegis/config"
	"github.com/go-chi/chi/v5"
	_ "github.com/lib/pq"
)

func main() {
	db, _ := sql.Open("postgres", "postgres://localhost/myapp?sslmode=disable")
	r := chi.NewRouter()

	// Create Aegis instance
	cfg := config.Default().
		WithDB(db).
		WithRouter(r).
		WithSecret([]byte("your-32-byte-secret-key-here!"))
	a, _ := aegis.New(context.Background(), cfg)

	// Mount authentication routes
	a.MountRoutes("/auth")

	// Use authentication middleware
	r.Use(a.AuthMiddleware())

	// Protected routes
	r.Group(func(r chi.Router) {
		r.Use(a.RequireAuth())
		r.Get("/api/profile", profileHandler)
	})

	http.ListenAndServe(":8080", r)
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetPluginTyped

func GetPluginTyped[T plugins.Plugin](a *Aegis, name string) (T, bool)

GetPluginTyped retrieves a plugin by name and type-casts it to the expected type.

This is a generic helper that combines GetPlugin() with type assertion. Use this when you know the plugin type and want compile-time type safety.

Parameters:

  • a: Aegis instance
  • name: Plugin name

Returns:

  • T: Plugin instance of the expected type
  • bool: true if found and type matches, false otherwise

Example:

oauthPlugin, ok := aegis.GetPluginTyped[*oauth.Plugin](aegis, "oauth")
if ok {
	// oauthPlugin is *oauth.Plugin, type-safe!
	providers := oauthPlugin.GetProviders()
}

Types

type Aegis

type Aegis struct {
	// contains filtered or unexported fields
}

Aegis is the main entry point for the Aegis authentication framework.

This struct coordinates all authentication subsystems:

  • Core authentication: User/account/session/verification services
  • Rate limiting: Request throttling and brute force protection
  • Plugin system: Extensible authentication methods (OAuth, JWT, etc.)
  • Middleware: HTTP handlers for authentication and authorization

Aegis is thread-safe and can be shared across goroutines. All plugin registration and configuration must be done before serving HTTP requests.

Architecture:

  • auth: Core services (User, Account, Session, Verification)
  • router: HTTP router for mounting routes
  • plugins: Registered plugins (OAuth, JWT, Organizations, etc.)
  • rateLimiter: Optional rate limiting for public endpoints
  • loginAttemptTracker: Brute force protection for login endpoints

Example:

a, err := aegis.New(ctx,
	config.WithDB(db),
	config.WithRouter(router),
	config.WithSecret(secret),
)
if err != nil {
	log.Fatal(err)
}

// Register plugins
a.Use(ctx, oauth.New(...))
a.Use(ctx, jwt.New(...))

// Mount routes
a.MountRoutes("/auth")

func New

func New(_ context.Context, cfg *config.Config) (*Aegis, error)

New creates a new Aegis authentication framework instance.

This function initializes all core services:

  • Database connection validation
  • Password hashing (Argon2id with OWASP parameters)
  • Session management (with optional Redis caching)
  • Rate limiting (if enabled)
  • Login attempt tracking (brute force protection)
  • Audit logging (security event tracking)

Configuration is provided via functional options (config.With* functions). Required options:

  • config.WithDB(db): Database connection (*sql.DB)
  • config.WithRouter(router): HTTP router implementing router.Router
  • config.WithSecret(secret): 32-byte master secret for key derivation

Optional configuration:

  • config.WithRedis(host, port, password, db): Enable Redis caching
  • config.WithRateLimiting(): Enable rate limiting
  • config.WithLogger(logger): Structured logging
  • config.WithAuditLogger(logger): Security audit logging
  • config.WithArgon2Time/WithArgon2Memory: Password hashing tuning

Parameters:

  • ctx: Context for initialization (can be canceled)
  • opts: Configuration options (config.With* functions)

Returns:

  • *Aegis: Configured framework instance
  • error: Configuration validation errors

Example:

db, _ := sql.Open("postgres", "postgres://localhost/myapp?sslmode=disable")
router := chi.NewRouter()
secret := []byte("your-32-byte-secret-key-here!")

a, err := aegis.New(context.Background(),
	config.WithDB(db),
	config.WithRouter(router),
	config.WithSecret(secret),
	config.WithRedis("localhost", 6379, "", 0),
	config.WithRateLimiting(),
)
if err != nil {
	log.Fatal("Failed to initialize Aegis:", err)
}

func (*Aegis) AuthMiddleware

func (a *Aegis) AuthMiddleware() func(http.Handler) http.Handler

AuthMiddleware returns middleware that validates sessions and injects user into context.

This is the PRIMARY middleware for Aegis authentication. It:

  1. Initializes request context (if not already done)
  2. Extracts session token from cookie or Authorization header
  3. Validates the session (database + Redis cache)
  4. Loads the authenticated user
  5. Creates EnrichedUser for plugin extensions
  6. Runs all UserEnricher plugins to populate extension fields
  7. Injects user and session into request context

After this middleware, handlers can access:

  • core.GetUser(ctx): Base user data
  • core.GetEnrichedUser(ctx): User with plugin extensions (role, org, etc.)
  • core.GetSession(ctx): Current session data

Plugin Extensions:

Plugins implementing UserEnricher automatically extend the user object:

  • Organizations plugin: Adds organization_id, organization fields
  • Admin plugin: Adds role, permissions fields
  • Custom plugins: Add any custom fields

All extension fields are flattened into the top-level JSON response.

Unauthenticated Requests:

This middleware does NOT block unauthenticated requests. It simply skips user injection if no valid session is found. Use RequireAuth() middleware for routes that MUST be authenticated.

Usage:

router := chi.NewRouter()

// Apply to all routes (authentication is optional)
router.Use(aegis.AuthMiddleware())

// Public routes (no auth required)
router.Get("/api/public", publicHandler)

// Protected routes (auth required)
router.Group(func(r chi.Router) {
	r.Use(aegis.RequireAuth())
	r.Get("/api/profile", profileHandler)
})

func (*Aegis) DeriveSecret

func (a *Aegis) DeriveSecret(purpose string) []byte

DeriveSecret derives a purpose-specific secret from the master secret.

func (*Aegis) GetAuthService

func (a *Aegis) GetAuthService() *core.AuthService

GetAuthService returns the core auth service

func (*Aegis) GetDB

func (a *Aegis) GetDB() *sql.DB

GetDB returns the database connection

func (*Aegis) GetLoginAttemptTracker

func (a *Aegis) GetLoginAttemptTracker() *core.LoginAttemptTracker

GetLoginAttemptTracker returns the login attempt tracker (may be nil if not enabled).

The login attempt tracker provides brute force protection by:

  • Counting failed login attempts per email/username
  • Locking accounts after too many failures (default: 5 in 15 minutes)
  • Automatically unlocking accounts after timeout (default: 15 minutes)

Use this to:

  • Check if an account is locked
  • Manually unlock accounts (admin functionality)
  • Customize lockout behavior

Returns nil if rate limiting is not enabled via config.WithRateLimiting().

func (*Aegis) GetPlugin

func (a *Aegis) GetPlugin(name string) (plugins.Plugin, bool)

GetPlugin retrieves a registered plugin by name.

Use this to access plugin-specific functionality after registration:

Parameters:

  • name: Plugin name (from plugin.Name())

Returns:

  • plugins.Plugin: The plugin instance
  • bool: true if found, false otherwise

Example:

if plugin, ok := aegis.GetPlugin("oauth"); ok {
	// Plugin is registered, use it
	oauthPlugin := plugin.(*oauth.Plugin)
	// ...
}

func (*Aegis) GetPlugins

func (a *Aegis) GetPlugins() []plugins.Plugin

GetPlugins returns all registered plugins sorted by priority.

Plugins are returned in priority order (lowest priority number first). This is the same order used for initialization and route mounting.

Use this to:

  • List all active plugins
  • Inspect plugin configuration
  • Debug plugin initialization order

Returns a copy of the plugin list (modifications don't affect Aegis).

Example:

for _, plugin := range aegis.GetPlugins() {
	fmt.Printf("Plugin: %s\n", plugin.Name())
}

func (*Aegis) GetRateLimiter

func (a *Aegis) GetRateLimiter() *core.RateLimiter

GetRateLimiter returns the rate limiter instance (may be nil if not enabled).

Use this to access rate limiting functionality directly:

  • Check rate limits programmatically
  • Implement custom rate limiting logic
  • Track rate limit status

Returns nil if rate limiting is not enabled via config.WithRateLimiting().

func (*Aegis) MountRoutes

func (a *Aegis) MountRoutes(prefix string)

MountRoutes mounts all authentication routes to the router with the given prefix.

This function registers HTTP handlers for:

Core Routes:

POST {prefix}/login - Email+password login (if enabled)
POST {prefix}/signup - Email+password registration (if enabled)
POST {prefix}/logout - Logout and invalidate session
GET  {prefix}/user - Get current user data
GET  {prefix}/session - Get current session info
GET  {prefix}/sessions - List all user sessions
POST {prefix}/session/refresh - Refresh session with refresh token
DELETE {prefix}/sessions/:id - Revoke specific session
DELETE {prefix}/sessions - Revoke all sessions

Plugin Routes:

Each registered plugin can mount additional routes under {prefix}/{plugin-name}:

  • OAuth: {prefix}/oauth/* - OAuth provider flows (Google, GitHub, etc.)
  • JWT: {prefix}/jwt/* - JWT token generation and validation
  • Organizations: {prefix}/organizations/* - Organization management
  • Admin: {prefix}/admin/* - Administrative user management
  • EmailOTP: {prefix}/email-otp/* - Email verification codes
  • SMS: {prefix}/sms/* - SMS verification codes

Plugin Mounting Order:

Plugins are mounted in priority order (lower priority numbers first):

  1. System plugins (priority 0-49)
  2. Authentication plugins (priority 50-99)
  3. Application plugins (priority 100+, default)

This ensures dependencies are initialized correctly (e.g., Organizations plugin is mounted before Admin plugin which depends on it).

Parameters:

  • prefix: URL prefix for all routes (e.g., "/auth", "/api/v1/auth")

Example:

a := aegis.New(...)
a.Use(ctx, oauth.New(...))  // Mounts at /auth/oauth/*
a.Use(ctx, jwt.New(...))    // Mounts at /auth/jwt/*
a.MountRoutes("/auth")      // Core routes at /auth/*

// Routes available:
// POST /auth/login
// POST /auth/signup
// POST /auth/logout
// GET  /auth/user
// POST /auth/oauth/google/login
// POST /auth/jwt/token
// etc.

func (*Aegis) RateLimitMiddleware

func (a *Aegis) RateLimitMiddleware() func(http.Handler) http.Handler

RateLimitMiddleware returns rate limiting middleware (if enabled).

This middleware implements sliding window rate limiting to prevent:

  • API abuse (too many requests)
  • Brute force attacks (password guessing)
  • DoS attacks (service exhaustion)

Rate limiting is configured via config.WithRateLimiting() during initialization. Default limits:

  • General endpoints: 100 requests per minute
  • Auth endpoints: 10 requests per minute

Rate limiting can use:

  • Redis: Distributed rate limiting across multiple servers (recommended)
  • In-memory: Single-server rate limiting (development/testing)

When a client exceeds the rate limit:

429 Too Many Requests
{
  "success": false,
  "error": "Rate limit exceeded. Please try again later."
}

Returns nil if rate limiting is not enabled (no-op).

Usage:

// Apply rate limiting to all routes
if rateLimitMW := aegis.RateLimitMiddleware(); rateLimitMW != nil {
	router.Use(rateLimitMW)
}

// Apply rate limiting to specific routes
router.With(aegis.RateLimitMiddleware()).Post("/api/expensive-operation", handler)

func (*Aegis) RequireAuth

func (a *Aegis) RequireAuth() func(http.Handler) http.Handler

RequireAuth returns middleware that requires authentication.

This middleware BLOCKS unauthenticated requests with 401 Unauthorized. Use this for protected routes that require a valid session.

This middleware should be applied AFTER AuthMiddleware() in the middleware chain:

  1. AuthMiddleware() - Validates session and injects user (optional)
  2. RequireAuth() - Blocks if no user in context (required)

Response on unauthenticated request:

401 Unauthorized
{
  "success": false,
  "error": "Authentication required"
}

Usage:

router := chi.NewRouter()
router.Use(aegis.AuthMiddleware())

// Protected group - all routes require authentication
router.Group(func(r chi.Router) {
	r.Use(aegis.RequireAuth())

	r.Get("/api/profile", profileHandler)
	r.Put("/api/profile", updateProfileHandler)
	r.Get("/api/settings", settingsHandler)
})

// Per-route protection
router.Get("/api/public", publicHandler)  // No auth required
router.With(aegis.RequireAuth()).Get("/api/private", privateHandler)  // Auth required

func (*Aegis) Use

func (a *Aegis) Use(ctx context.Context, plugin plugins.Plugin) error

Use registers a plugin with the Aegis instance using default priority (100).

Plugins extend Aegis with additional authentication methods and features:

  • OAuth: Social login (Google, GitHub, Facebook, etc.)
  • JWT: Token-based authentication
  • Organizations: Multi-tenant organization support
  • Admin: Administrative user management dashboard
  • EmailOTP: Email verification codes
  • SMS: Phone number verification
  • Bearer: Bearer token authentication
  • OpenAPI: Auto-generated API documentation

Plugin initialization:

  1. Plugin.Init() is called with the Aegis instance
  2. Plugin can access database, auth services, rate limiter, etc.
  3. Plugin is registered in the plugin map
  4. Plugin routes are NOT mounted yet (call MountRoutes after all plugins registered)

Plugin registration must happen BEFORE MountRoutes() is called.

Default priority is 100 (application plugins). For custom priority, use UseWithPriority.

Parameters:

  • ctx: Context for plugin initialization (can be canceled)
  • plugin: Plugin instance to register

Returns:

  • error: Plugin initialization errors

Example:

a := aegis.New(...)

// Register OAuth plugin for social login
oauthPlugin := oauth.New(oauthConfig, keyManager, dialect)
if err := a.Use(ctx, oauthPlugin); err != nil {
	log.Fatal("Failed to register OAuth plugin:", err)
}

// Register JWT plugin for token authentication
jwtPlugin := jwt.New(jwtConfig, keyManager, dialect)
if err := a.Use(ctx, jwtPlugin); err != nil {
	log.Fatal("Failed to register JWT plugin:", err)
}

// Mount all routes (including plugin routes)
a.MountRoutes("/auth")

func (*Aegis) UseWithPriority

func (a *Aegis) UseWithPriority(ctx context.Context, plugin plugins.Plugin, priority int) error

UseWithPriority registers a plugin with an explicit initialization priority.

Priority controls the order of plugin initialization and route mounting:

  • Priority 0-49: System plugins (organizations, core extensions)
  • Priority 50-99: Authentication plugins (OAuth, JWT, EmailOTP, SMS)
  • Priority 100+: Application plugins (custom plugins, default)

Lower priority numbers are initialized and mounted first. This allows plugins to depend on other plugins (e.g., Admin plugin depends on Organizations plugin, so Organizations has lower priority).

Use this when:

  • Your plugin depends on another plugin
  • You need to control route mounting order
  • You're building system-level plugins

Most applications should use Use() with default priority (100).

Parameters:

  • ctx: Context for plugin initialization
  • plugin: Plugin instance to register
  • priority: Initialization/mounting priority (lower = earlier)

Example:

// Register Organizations plugin first (priority 10)
orgPlugin := organizations.New(orgConfig, dialect)
a.UseWithPriority(ctx, orgPlugin, 10)

// Register Admin plugin second (priority 50, depends on Organizations)
adminPlugin := admin.New(adminConfig, dialect)
a.UseWithPriority(ctx, adminPlugin, 50)

func (*Aegis) ValidateSchemaRequirements

func (a *Aegis) ValidateSchemaRequirements(ctx context.Context, requirements []plugins.SchemaRequirement) error

ValidateSchemaRequirements validates that the database has the required tables.

Directories

Path Synopsis
Package auth provides the core data layer for Aegis authentication system.
Package auth provides the core data layer for Aegis authentication system.
cmd
aegis command
Package main provides the Aegis CLI tool for database migration management.
Package main provides the Aegis CLI tool for database migration management.
Package config provides configuration types and options for Aegis.
Package config provides configuration types and options for Aegis.
Package core provides the high-level authentication and authorization business logic.
Package core provides the high-level authentication and authorization business logic.
Package exporter provides functionality for exporting database migrations and schemas.
Package exporter provides functionality for exporting database migrations and schemas.
Package plugins defines the plugin interface and core plugin types for Aegis.
Package plugins defines the plugin interface and core plugin types for Aegis.
admin
Package admin provides role-based access control (RBAC) and administrative user management.
Package admin provides role-based access control (RBAC) and administrative user management.
emailotp
Package emailotp provides email-based OTP (One-Time Password) verification and authentication.
Package emailotp provides email-based OTP (One-Time Password) verification and authentication.
jwt
Package jwt implements JWT (JSON Web Token) authentication for Aegis.
Package jwt implements JWT (JSON Web Token) authentication for Aegis.
oauth
Package oauth provides database migration management for the OAuth plugin.
Package oauth provides database migration management for the OAuth plugin.
openapi
Package openapi provides automatic OpenAPI 3.0 specification generation for Aegis.
Package openapi provides automatic OpenAPI 3.0 specification generation for Aegis.
organizations
Package organizations provides multi-tenancy and team management for Aegis.
Package organizations provides multi-tenancy and team management for Aegis.
sms
Package sms provides phone-based OTP (One-Time Password) verification and authentication.
Package sms provides phone-based OTP (One-Time Password) verification and authentication.
Package router provides router adapters for the Aegis authentication framework.
Package router provides router adapters for the Aegis authentication framework.
Package testing provides shared test utilities for Aegis integration tests.
Package testing provides shared test utilities for Aegis integration tests.

Jump to

Keyboard shortcuts

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