auth

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Nov 19, 2025 License: Apache-2.0 Imports: 9 Imported by: 0

README

Forge v2: Authentication Extension

Production-ready authentication system with automatic OpenAPI security scheme generation.

Features

  • Multiple Auth Providers - API Key, Bearer Token (JWT), OAuth2, OpenID Connect, Basic Auth
  • Auto OpenAPI Generation - Security schemes automatically added to OpenAPI specs
  • DI Container Access - Validators can access services for database lookups, caching, etc.
  • Flexible Configuration - Route-level and group-level auth with OR/AND logic
  • Scope/Permission Support - Fine-grained access control with required scopes
  • Thread-Safe - Concurrent access to auth registry
  • Production-Ready - Proper error handling, validation, and logging

Quick Start

1. Register the Auth Extension
import (
    "github.com/xraph/forge"
    "github.com/xraph/forge/extensions/auth"
    "github.com/xraph/forge/extensions/auth/providers"
)

func main() {
    app := forge.NewApp(forge.DefaultAppConfig())
    
    // Register auth extension
    authExt := auth.NewExtension()
    app.RegisterExtension(authExt)
    
    app.Run()
}
2. Register Auth Providers
// Get auth registry
registry := forge.Must[auth.Registry](app.Container(), "auth:registry")

// Register API Key provider
apiKeyProvider := providers.NewAPIKeyProvider("api-key",
    providers.WithAPIKeyHeader("X-API-Key"),
    providers.WithAPIKeyValidator(func(ctx context.Context, key string) (*auth.AuthContext, error) {
        // Validate against database or cache
        if key == "secret-key-123" {
            return &auth.AuthContext{
                Subject: "user-123",
                Claims: map[string]interface{}{
                    "role": "admin",
                },
            }, nil
        }
        return nil, auth.ErrInvalidCredentials
    }),
)
registry.Register(apiKeyProvider)

// Register JWT Bearer provider
jwtProvider := providers.NewBearerTokenProvider("jwt",
    providers.WithBearerFormat("JWT"),
    providers.WithBearerValidator(func(ctx context.Context, token string) (*auth.AuthContext, error) {
        // Validate JWT token
        // Access services from DI container
        // jwtService := forge.Must[JWTService](app.Container(), "jwt")
        // claims, err := jwtService.Verify(token)
        
        return &auth.AuthContext{
            Subject: "user-from-jwt",
            Scopes:  []string{"read:users", "write:users"},
        }, nil
    }),
)
registry.Register(jwtProvider)
3. Protect Routes
router := app.Router()

// Public route (no auth)
router.GET("/public", publicHandler)

// Protected route with API Key OR JWT
router.GET("/protected", protectedHandler,
    forge.WithAuth("api-key", "jwt"),
    forge.WithSummary("Protected endpoint"),
)

// Route requiring specific scopes
router.POST("/admin/users", adminHandler,
    forge.WithRequiredAuth("jwt", "write:users", "admin"),
    forge.WithSummary("Create user (Admin only)"),
)

// Group with auth (all routes inherit)
api := router.Group("/api/v1", forge.WithGroupAuth("jwt"))
{
    api.GET("/users", listUsersHandler)
    api.GET("/users/:id", getUserHandler)
    api.POST("/users", createUserHandler)
}
4. Access Auth Context in Handlers
func protectedHandler(w http.ResponseWriter, r *http.Request) error {
    // Get auth context
    authCtx, ok := auth.FromContext(r.Context())
    if !ok {
        return forge.Unauthorized("not authenticated")
    }
    
    return forge.JSON(w, 200, map[string]interface{}{
        "user":     authCtx.Subject,
        "provider": authCtx.ProviderName,
        "scopes":   authCtx.Scopes,
        "claims":   authCtx.Claims,
    })
}

Built-in Providers

API Key Provider

Supports API keys in headers, query parameters, or cookies.

provider := providers.NewAPIKeyProvider("api-key",
    providers.WithAPIKeyHeader("X-API-Key"),        // Header
    providers.WithAPIKeyQuery("api_key"),           // Query param
    providers.WithAPIKeyCookie("api_key"),          // Cookie
    providers.WithAPIKeyDescription("API Key Authentication"),
    providers.WithAPIKeyValidator(validateFunc),
)

OpenAPI Output:

securitySchemes:
  api-key:
    type: apiKey
    name: X-API-Key
    in: header
Bearer Token Provider (JWT)

For JWT tokens or other bearer tokens.

provider := providers.NewBearerTokenProvider("jwt",
    providers.WithBearerFormat("JWT"),
    providers.WithBearerDescription("JWT Bearer Authentication"),
    providers.WithBearerValidator(validateFunc),
)

OpenAPI Output:

securitySchemes:
  jwt:
    type: http
    scheme: bearer
    bearerFormat: JWT
Basic Auth Provider

HTTP Basic Authentication.

provider := providers.NewBasicAuthProvider("basic",
    providers.WithBasicAuthDescription("HTTP Basic Authentication"),
    providers.WithBasicAuthValidator(func(ctx context.Context, username, password string) (*auth.AuthContext, error) {
        // Validate credentials
        return &auth.AuthContext{Subject: username}, nil
    }),
)

OpenAPI Output:

securitySchemes:
  basic:
    type: http
    scheme: basic
OAuth2 Provider

OAuth 2.0 with configurable flows.

provider := providers.NewOAuth2Provider("oauth2",
    &auth.OAuthFlows{
        AuthorizationCode: &auth.OAuthFlow{
            AuthorizationURL: "https://example.com/oauth/authorize",
            TokenURL:         "https://example.com/oauth/token",
            Scopes: map[string]string{
                "read":  "Read access",
                "write": "Write access",
            },
        },
    },
    providers.WithOAuth2Validator(validateFunc),
)

OpenAPI Output:

securitySchemes:
  oauth2:
    type: oauth2
    flows:
      authorizationCode:
        authorizationUrl: https://example.com/oauth/authorize
        tokenUrl: https://example.com/oauth/token
        scopes:
          read: Read access
          write: Write access
OpenID Connect Provider

OpenID Connect authentication.

provider := providers.NewOIDCProvider("oidc",
    "https://example.com/.well-known/openid-configuration",
    providers.WithOIDCValidator(validateFunc),
)

Route Options

Single Provider (OR Logic)
// Accepts either api-key OR jwt
router.GET("/protected", handler,
    forge.WithAuth("api-key", "jwt"),
)

OpenAPI:

security:
  - api-key: []
  - jwt: []
Required Scopes
// Requires jwt with specific scopes
router.POST("/admin", handler,
    forge.WithRequiredAuth("jwt", "write:users", "admin"),
)

OpenAPI:

security:
  - jwt: [write:users, admin]
Multiple Providers (AND Logic)
// Requires BOTH api-key AND jwt (rare use case)
router.GET("/high-security", handler,
    forge.WithAuthAnd("api-key", "jwt"),
)

OpenAPI:

security:
  - api-key: []
    jwt: []

Group Options

Apply authentication to all routes in a group:

// All routes in this group require JWT
api := router.Group("/api/v1",
    forge.WithGroupAuth("jwt"),
    forge.WithGroupRequiredScopes("read"),
)

// Override group auth for specific route
api.GET("/public-in-group", handler, forge.WithAuth()) // No auth

Custom Auth Providers

Implement the AuthProvider interface:

type MyCustomProvider struct {
    name      string
    container forge.Container
}

func (p *MyCustomProvider) Name() string {
    return p.name
}

func (p *MyCustomProvider) Type() auth.SecuritySchemeType {
    return auth.SecurityTypeHTTP
}

func (p *MyCustomProvider) Authenticate(ctx context.Context, r *http.Request) (*auth.AuthContext, error) {
    // Your custom authentication logic
    // Access DI container services via p.container
    
    return &auth.AuthContext{
        Subject: "user-id",
        Claims:  map[string]interface{}{"custom": "data"},
    }, nil
}

func (p *MyCustomProvider) OpenAPIScheme() auth.SecurityScheme {
    return auth.SecurityScheme{
        Type:        auth.SecurityTypeHTTP,
        Description: "My custom authentication",
        Scheme:      "custom",
    }
}

func (p *MyCustomProvider) Middleware() forge.Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authCtx, err := p.Authenticate(r.Context(), r)
            if err != nil {
                http.Error(w, "Unauthorized", http.StatusUnauthorized)
                return
            }
            ctx := auth.WithContext(r.Context(), authCtx)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// Register it
registry.Register(&MyCustomProvider{name: "custom"})

AuthContext API

The AuthContext contains authenticated user information:

type AuthContext struct {
    Subject      string                 // User/service ID
    Claims       map[string]interface{} // Additional claims
    Scopes       []string               // Permissions/scopes
    Metadata     map[string]interface{} // Provider-specific data
    ProviderName string                 // Which provider authenticated
}

// Helper methods
authCtx.HasScope("admin")              // Check single scope
authCtx.HasScopes("read", "write")     // Check multiple scopes
authCtx.GetClaim("email")              // Get claim value
authCtx.GetClaimString("role")         // Get string claim

DI Container Access

Validators have access to the DI container for service lookup:

// Create provider with container access
apiKeyProvider := providers.NewAPIKeyProvider("api-key",
    providers.WithAPIKeyContainer(app.Container()),
    providers.WithAPIKeyValidator(func(ctx context.Context, apiKey string) (*auth.AuthContext, error) {
        // Access services from container
        db, err := database.GetDatabase(app.Container())
        if err != nil {
            return nil, err
        }
        cache := forge.Must[Cache](app.Container(), "cache")
        
        // Check cache first
        if user, found := cache.Get(ctx, "apikey:"+apiKey); found {
            return user.(*auth.AuthContext), nil
        }
        
        // Query database
        user, err := db.FindUserByAPIKey(ctx, apiKey)
        if err != nil {
            return nil, auth.ErrInvalidCredentials
        }
        
        // Cache for next time
        authCtx := &auth.AuthContext{
            Subject: user.ID,
            Claims:  map[string]interface{}{"email": user.Email},
        }
        cache.Set(ctx, "apikey:"+apiKey, authCtx, 5*time.Minute)
        
        return authCtx, nil
    }),
)

Error Handling

The auth extension provides standard errors:

var (
    ErrProviderNotFound     = errors.New("auth provider not found")
    ErrProviderExists       = errors.New("auth provider already exists")
    ErrInvalidConfiguration = errors.New("invalid auth configuration")
    ErrMissingCredentials   = errors.New("missing authentication credentials")
    ErrInvalidCredentials   = errors.New("invalid credentials")
    ErrInsufficientScopes   = errors.New("insufficient scopes")
    ErrAuthenticationFailed = errors.New("authentication failed")
    ErrAuthorizationFailed  = errors.New("authorization failed")
    ErrTokenExpired         = errors.New("token expired")
    ErrTokenInvalid         = errors.New("invalid token")
)

Testing

Mock providers for testing:

type MockAuthProvider struct{}

func (m *MockAuthProvider) Name() string { return "mock" }
func (m *MockAuthProvider) Type() auth.SecuritySchemeType { return auth.SecurityTypeAPIKey }

func (m *MockAuthProvider) Authenticate(ctx context.Context, r *http.Request) (*auth.AuthContext, error) {
    return &auth.AuthContext{Subject: "test-user"}, nil
}

// ... implement other methods

// In tests
registry.Register(&MockAuthProvider{})

Configuration

Load configuration from ConfigManager:

// config.yaml
extensions:
  auth:
    enabled: true
    default_provider: "jwt"
    providers:
      - name: "api-key"
        type: "apiKey"
        enabled: true

// Create extension with config loading
authExt := auth.NewExtension(auth.WithRequireConfig(true))

Best Practices

1. Use Appropriate Auth for Use Case
  • API Keys - Machine-to-machine, service accounts
  • JWT Bearer - User sessions, SPAs, mobile apps
  • OAuth2 - Third-party integrations
  • Basic Auth - Simple internal tools, development
2. Validate Thoroughly
providers.WithBearerValidator(func(ctx context.Context, token string) (*auth.AuthContext, error) {
    // 1. Verify signature
    // 2. Check expiration
    // 3. Validate issuer/audience
    // 4. Check revocation list
    // 5. Verify scopes/permissions
    
    return authCtx, nil
})
3. Use Scopes for Fine-Grained Control
// Define clear scope hierarchy
const (
    ScopeReadUsers  = "read:users"
    ScopeWriteUsers = "write:users"
    ScopeAdmin      = "admin"
)

router.POST("/users", handler,
    forge.WithRequiredAuth("jwt", ScopeWriteUsers),
)
4. Cache Validation Results
// Cache JWT validation results by token
cache.Set(ctx, "jwt:"+tokenHash, authCtx, tokenTTL)
5. Log Authentication Events
logger.Info("authentication succeeded",
    "provider", authCtx.ProviderName,
    "subject", authCtx.Subject,
    "ip", r.RemoteAddr,
)
6. Use Rate Limiting

Combine with rate limiting middleware to prevent brute force attacks.

Examples

See examples/auth_example for a complete working example with:

  • Multiple auth providers
  • Route and group protection
  • Scope validation
  • OpenAPI documentation
  • DI container integration

Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Auth Extension                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌───────────────┐          ┌──────────────────────────┐    │
│  │   Registry    │          │    Auth Providers        │    │
│  │               │◄─────────┤  - API Key               │    │
│  │ - Register    │          │  - Bearer Token (JWT)    │    │
│  │ - Get         │          │  - Basic Auth            │    │
│  │ - Middleware  │          │  - OAuth2                │    │
│  │ - OpenAPI     │          │  - OpenID Connect        │    │
│  └───────┬───────┘          │  - Custom                │    │
│          │                  └──────────────────────────┘    │
│          │                                                  │
│          ▼                                                  │
│  ┌────────────────────────────────────────────────────────┐ │
│  │              Router Integration                        │ │
│  │  - WithAuth()                                          │ │
│  │  - WithRequiredAuth()                                  │ │
│  │  - WithGroupAuth()                                     │ │
│  └───────────────────────────────────────────────────────-┘ │
│          │                                                  │
│          ▼                                                  │
│  ┌────────────────────────────────────────────────────────┐ │
│  │          OpenAPI Generator Integration                 │ │
│  │  - Auto-generates security schemes                     │ │
│  │  - Adds security requirements to routes                │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

License

Part of the Forge v2 framework.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrProviderNotFound is returned when a provider is not found.
	ErrProviderNotFound = errors.New("auth provider not found")

	// ErrProviderExists is returned when a provider already exists.
	ErrProviderExists = errors.New("auth provider already exists")

	// ErrInvalidConfiguration is returned when configuration is invalid.
	ErrInvalidConfiguration = errors.New("invalid auth configuration")

	// ErrMissingCredentials is returned when credentials are missing.
	ErrMissingCredentials = errors.New("missing authentication credentials")

	// ErrInvalidCredentials is returned when credentials are invalid.
	ErrInvalidCredentials = errors.New("invalid credentials")

	// ErrInsufficientScopes is returned when required scopes are missing.
	ErrInsufficientScopes = errors.New("insufficient scopes")

	// ErrAuthenticationFailed is returned when authentication fails.
	ErrAuthenticationFailed = errors.New("authentication failed")

	// ErrAuthorizationFailed is returned when authorization fails.
	ErrAuthorizationFailed = errors.New("authorization failed")

	// ErrTokenExpired is returned when a token has expired.
	ErrTokenExpired = errors.New("token expired")

	// ErrTokenInvalid is returned when a token is invalid.
	ErrTokenInvalid = errors.New("invalid token")
)

Functions

func NewExtension

func NewExtension(opts ...ConfigOption) forge.Extension

NewExtension creates a new auth extension with functional options.

Example:

auth.NewExtension(
    auth.WithEnabled(true),
    auth.WithDefaultProvider("jwt"),
)

func NewExtensionWithConfig

func NewExtensionWithConfig(config Config) forge.Extension

NewExtensionWithConfig creates a new auth extension with a complete config. This is for backward compatibility or when config is fully known at initialization.

func WithContext

func WithContext(ctx context.Context, authCtx *AuthContext) context.Context

WithContext adds the AuthContext to a context.

Types

type AuthContext

type AuthContext struct {
	// Subject is the authenticated entity (user ID, service ID, etc.)
	Subject string

	// Claims holds additional authentication claims (roles, permissions, etc.)
	Claims map[string]any

	// Scopes holds OAuth2 scopes or permission strings
	Scopes []string

	// Metadata holds provider-specific metadata
	Metadata map[string]any

	// Data holds additional data from the authenticated provider
	Data any

	// ProviderName identifies which auth provider authenticated this request
	ProviderName string
}

AuthContext holds authenticated user/service information. This is stored in the request context after successful authentication.

func FromContext

func FromContext(ctx context.Context) (*AuthContext, bool)

FromContext retrieves the AuthContext from the request context. Returns the auth context and true if found, nil and false otherwise.

func GetAuthContext added in v0.5.0

func GetAuthContext(ctx forge.Context) (*AuthContext, bool)

GetAuthContext retrieves the AuthContext from forge.Context. Returns the auth context and true if found, nil and false otherwise.

func GetAuthContextFromStdContext added in v0.5.0

func GetAuthContextFromStdContext(ctx context.Context) (*AuthContext, bool)

GetAuthContextFromStdContext retrieves AuthContext from standard context.Context. This is for backward compatibility with code that uses standard context. For new code using forge.Context, use GetAuthContext instead.

func MustFromContext

func MustFromContext(ctx context.Context) *AuthContext

MustFromContext retrieves the AuthContext or panics. Use this only when you're certain the context contains auth information (e.g., after auth middleware has run).

func MustGetAuthContext added in v0.5.0

func MustGetAuthContext(ctx forge.Context) *AuthContext

MustGetAuthContext retrieves the AuthContext from forge.Context or panics. Use this only when you're certain the context contains auth information (e.g., after auth middleware has run).

func (*AuthContext) GetClaim

func (a *AuthContext) GetClaim(key string) (any, bool)

GetClaim retrieves a claim by key.

func (*AuthContext) GetClaimString

func (a *AuthContext) GetClaimString(key string) (string, bool)

GetClaimString retrieves a string claim.

func (*AuthContext) HasScope

func (a *AuthContext) HasScope(scope string) bool

HasScope checks if the auth context has a specific scope.

func (*AuthContext) HasScopes

func (a *AuthContext) HasScopes(scopes ...string) bool

HasScopes checks if the auth context has all specified scopes.

type AuthProvider

type AuthProvider interface {
	// Name returns the unique name/ID of this auth provider.
	// This name is used to reference the provider in route/group options.
	Name() string

	// Type returns the OpenAPI security scheme type
	Type() SecuritySchemeType

	// Authenticate validates the request and returns the authenticated subject.
	// Returns the auth context (user, claims, etc.) or an error.
	// The provider can access services from the DI container via closures.
	Authenticate(ctx context.Context, r *http.Request) (*AuthContext, error)

	// OpenAPIScheme returns the OpenAPI security scheme definition.
	// This is used to automatically generate OpenAPI security documentation.
	OpenAPIScheme() SecurityScheme

	// Middleware returns HTTP middleware for this provider.
	// The middleware is automatically applied when the provider is used.
	Middleware() forge.Middleware
}

AuthProvider defines the interface for authentication providers. Implementations can access the DI container through closures or context to retrieve services needed for authentication (database, cache, etc.).

type Config

type Config struct {
	// Enabled determines if auth extension is enabled
	Enabled bool

	// Providers holds provider-specific configurations
	Providers []ProviderConfig

	// DefaultProvider is the default provider name to use
	DefaultProvider string

	// RequireConfig requires config from ConfigManager
	RequireConfig bool
}

Config holds auth extension configuration.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default auth configuration.

type ConfigOption

type ConfigOption func(*Config)

ConfigOption configures the auth extension.

func WithConfig

func WithConfig(config Config) ConfigOption

WithConfig sets the complete config.

func WithDefaultProvider

func WithDefaultProvider(name string) ConfigOption

WithDefaultProvider sets the default provider.

func WithEnabled

func WithEnabled(enabled bool) ConfigOption

WithEnabled sets whether auth is enabled.

func WithProviders

func WithProviders(providers ...ProviderConfig) ConfigOption

WithProviders sets the provider configurations.

func WithRequireConfig

func WithRequireConfig(require bool) ConfigOption

WithRequireConfig requires config from ConfigManager.

type Extension

type Extension struct {
	*forge.BaseExtension
	// contains filtered or unexported fields
}

Extension implements forge.Extension for authentication and authorization. It provides a registry for managing auth providers and automatically integrates with the router for OpenAPI security scheme generation.

func (*Extension) Health

func (e *Extension) Health(ctx context.Context) error

Health checks the auth extension health.

func (*Extension) Register

func (e *Extension) Register(app forge.App) error

Register registers the auth extension with the app. It creates and registers the auth provider registry as a singleton service.

func (*Extension) Registry

func (e *Extension) Registry() Registry

Registry returns the auth provider registry. This allows external code to register custom auth providers.

func (*Extension) Start

func (e *Extension) Start(ctx context.Context) error

Start starts the auth extension.

func (*Extension) Stop

func (e *Extension) Stop(ctx context.Context) error

Stop stops the auth extension gracefully.

type OAuthFlow

type OAuthFlow = shared.OAuthFlow

OAuthFlow defines a single OAuth 2.0 flow.

type OAuthFlows

type OAuthFlows = shared.OAuthFlows

OAuthFlows defines OAuth 2.0 flows.

type ProviderConfig

type ProviderConfig struct {
	Name        string
	Type        string
	Enabled     bool
	Description string
	Config      map[string]any
}

ProviderConfig holds configuration for a single auth provider.

type ProviderFunc

type ProviderFunc func(ctx context.Context, r *http.Request) (*AuthContext, error)

ProviderFunc is a function adapter for simple auth providers.

type Registry

type Registry interface {
	// Register registers an auth provider
	Register(provider AuthProvider) error

	// Unregister removes a provider by name
	Unregister(name string) error

	// Get retrieves a provider by name
	Get(name string) (AuthProvider, error)

	// Has checks if a provider exists
	Has(name string) bool

	// List returns all registered provider names
	List() []string

	// Middleware creates combined middleware for multiple providers.
	// When multiple providers are specified, they are tried in order (OR logic).
	// Authentication succeeds if ANY provider succeeds.
	Middleware(providerNames ...string) forge.Middleware

	// MiddlewareAnd creates middleware requiring ALL providers to succeed (AND logic).
	MiddlewareAnd(providerNames ...string) forge.Middleware

	// MiddlewareWithScopes creates middleware with required scopes
	MiddlewareWithScopes(providerName string, scopes ...string) forge.Middleware

	// OpenAPISchemes returns all security schemes for OpenAPI generation
	OpenAPISchemes() map[string]SecurityScheme
}

Registry manages authentication providers. It provides thread-safe registration and retrieval of auth providers, and can create middleware that chains multiple providers.

func NewRegistry

func NewRegistry(container forge.Container, logger forge.Logger) Registry

NewRegistry creates a new auth provider registry.

type SecurityScheme

type SecurityScheme = shared.SecurityScheme

SecurityScheme represents an OpenAPI security scheme definition We use the shared type to ensure compatibility with OpenAPI generation.

type SecuritySchemeType

type SecuritySchemeType string

SecuritySchemeType represents OpenAPI 3.1 security scheme types.

const (
	SecurityTypeAPIKey        SecuritySchemeType = "apiKey"
	SecurityTypeHTTP          SecuritySchemeType = "http"
	SecurityTypeOAuth2        SecuritySchemeType = "oauth2"
	SecurityTypeOpenIDConnect SecuritySchemeType = "openIdConnect"
	SecurityTypeMutualTLS     SecuritySchemeType = "mutualTLS"
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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