Production-ready authentication system with automatic OpenAPI security scheme generation.
- ✅ 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
If you use LDAP with StartTLS, install your LDAP certificate authority in the
host trust store before connecting. The provider verifies the certificate and
hostname and requires TLS 1.2 or newer. InsecureSkipVerify remains in
LDAPConfig so existing code and configuration still decode, but setting it to
true returns an error before any connection attempt. Set it to false after
configuring trust. Deployments that relied on unverified certificates must
configure their CA before upgrading.
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()
}// 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)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)
}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,
})
}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: headerFor 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: JWTHTTP 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: basicOAuth 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 accessOpenID Connect authentication.
provider := providers.NewOIDCProvider("oidc",
"https://example.com/.well-known/openid-configuration",
providers.WithOIDCValidator(validateFunc),
)// Accepts either api-key OR jwt
router.GET("/protected", handler,
forge.WithAuth("api-key", "jwt"),
)OpenAPI:
security:
- api-key: []
- jwt: []// Requires jwt with specific scopes
router.POST("/admin", handler,
forge.WithRequiredAuth("jwt", "write:users", "admin"),
)OpenAPI:
security:
- jwt: [write:users, admin]// Requires BOTH api-key AND jwt (rare use case)
router.GET("/high-security", handler,
forge.WithAuthAnd("api-key", "jwt"),
)OpenAPI:
security:
- api-key: []
jwt: []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 authImplement 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"})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 claimValidators 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
}),
)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")
)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{})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))- API Keys - Machine-to-machine, service accounts
- JWT Bearer - User sessions, SPAs, mobile apps
- OAuth2 - Third-party integrations
- Basic Auth - Simple internal tools, development
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
})// Define clear scope hierarchy
const (
ScopeReadUsers = "read:users"
ScopeWriteUsers = "write:users"
ScopeAdmin = "admin"
)
router.POST("/users", handler,
forge.WithRequiredAuth("jwt", ScopeWriteUsers),
)// Cache JWT validation results by token
cache.Set(ctx, "jwt:"+tokenHash, authCtx, tokenTTL)logger.Info("authentication succeeded",
"provider", authCtx.ProviderName,
"subject", authCtx.Subject,
"ip", r.RemoteAddr,
)Combine with rate limiting middleware to prevent brute force attacks.
See examples/auth_example for a complete working example with:
- Multiple auth providers
- Route and group protection
- Scope validation
- OpenAPI documentation
- DI container integration
┌─────────────────────────────────────────────────────────────┐
│ 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 │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Part of the Forge framework.