Auth Extension
Comprehensive authentication and authorization with multiple providers and security features
Auth Extension
The Auth extension provides comprehensive authentication and authorization capabilities with support for multiple providers, middleware integration, and advanced security features.
The Auth extension supports JWT, OAuth2, SAML, LDAP, and custom authentication providers with unified interfaces.
Features
Authentication Providers
- JWT: JSON Web Token authentication
- OAuth2: OAuth 2.0 with multiple providers (Google, GitHub, etc.)
- SAML: Security Assertion Markup Language
- LDAP: Lightweight Directory Access Protocol
- Basic Auth: Username/password authentication
- API Keys: Token-based authentication
- Custom: Build your own authentication provider
Security Features
- Multi-Factor Authentication (MFA): TOTP, SMS, Email
- Session Management: Secure session handling
- Rate Limiting: Prevent brute force attacks
- Password Policies: Enforce strong passwords
- Account Lockout: Temporary account suspension
- Audit Logging: Track authentication events
Authorization
- Role-Based Access Control (RBAC): Define roles and permissions
- Attribute-Based Access Control (ABAC): Fine-grained permissions
- Resource-Based Permissions: Control access to specific resources
- Dynamic Permissions: Runtime permission evaluation
Installation
go get github.com/xraph/forge/extensions/authConfiguration
extensions:
auth:
# Default provider
default_provider: "jwt"
# JWT Configuration
jwt:
secret: "${JWT_SECRET}"
issuer: "forge-app"
audience: "forge-users"
expiry: "24h"
refresh_expiry: "7d"
algorithm: "HS256"
# OAuth2 Configuration
oauth2:
providers:
google:
client_id: "${GOOGLE_CLIENT_ID}"
client_secret: "${GOOGLE_CLIENT_SECRET}"
redirect_url: "http://localhost:8080/auth/google/callback"
scopes: ["email", "profile"]
github:
client_id: "${GITHUB_CLIENT_ID}"
client_secret: "${GITHUB_CLIENT_SECRET}"
redirect_url: "http://localhost:8080/auth/github/callback"
scopes: ["user:email"]
# LDAP Configuration
ldap:
host: "ldap.company.com"
port: 389
bind_dn: "cn=admin,dc=company,dc=com"
bind_password: "${LDAP_PASSWORD}"
base_dn: "ou=users,dc=company,dc=com"
user_filter: "(uid=%s)"
tls: true
# Security Settings
security:
password_policy:
min_length: 8
require_uppercase: true
require_lowercase: true
require_numbers: true
require_symbols: true
mfa:
enabled: true
providers: ["totp", "sms"]
backup_codes: true
rate_limiting:
enabled: true
max_attempts: 5
window: "15m"
lockout_duration: "30m"
session:
secure: true
http_only: true
same_site: "strict"
max_age: "24h"
# RBAC Configuration
rbac:
enabled: true
roles:
admin:
permissions: ["*"]
user:
permissions: ["read:profile", "update:profile"]
guest:
permissions: ["read:public"]# JWT Configuration
export JWT_SECRET="your-super-secret-key"
export JWT_ISSUER="forge-app"
export JWT_EXPIRY="24h"
# OAuth2 Providers
export GOOGLE_CLIENT_ID="your-google-client-id"
export GOOGLE_CLIENT_SECRET="your-google-client-secret"
export GITHUB_CLIENT_ID="your-github-client-id"
export GITHUB_CLIENT_SECRET="your-github-client-secret"
# LDAP Configuration
export LDAP_HOST="ldap.company.com"
export LDAP_BIND_DN="cn=admin,dc=company,dc=com"
export LDAP_PASSWORD="ldap-password"
# Security Settings
export AUTH_MFA_ENABLED="true"
export AUTH_RATE_LIMITING_ENABLED="true"
export AUTH_SESSION_SECURE="true"package main
import (
"github.com/xraph/forge"
"github.com/xraph/forge/extensions/auth"
)
func main() {
app := forge.New()
// Configure Auth extension
authExt := auth.New(auth.Config{
DefaultProvider: "jwt",
JWT: auth.JWTConfig{
Secret: "your-super-secret-key",
Issuer: "forge-app",
Audience: "forge-users",
Expiry: time.Hour * 24,
Algorithm: "HS256",
},
OAuth2: auth.OAuth2Config{
Providers: map[string]auth.OAuth2Provider{
"google": {
ClientID: "your-google-client-id",
ClientSecret: "your-google-client-secret",
RedirectURL: "http://localhost:8080/auth/google/callback",
Scopes: []string{"email", "profile"},
},
},
},
Security: auth.SecurityConfig{
PasswordPolicy: auth.PasswordPolicy{
MinLength: 8,
RequireUppercase: true,
RequireLowercase: true,
RequireNumbers: true,
RequireSymbols: true,
},
MFA: auth.MFAConfig{
Enabled: true,
Providers: []string{"totp", "sms"},
BackupCodes: true,
},
RateLimiting: auth.RateLimitingConfig{
Enabled: true,
MaxAttempts: 5,
Window: time.Minute * 15,
LockoutDuration: time.Minute * 30,
},
},
RBAC: auth.RBACConfig{
Enabled: true,
Roles: map[string]auth.Role{
"admin": {
Permissions: []string{"*"},
},
"user": {
Permissions: []string{"read:profile", "update:profile"},
},
},
},
})
app.RegisterExtension(authExt)
app.Run()
}Usage Examples
Basic Authentication
func loginHandler(c forge.Context) error {
auth := forge.GetAuth(c)
var req struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request"})
}
// Authenticate user
user, err := auth.Authenticate(c.Context(), auth.Credentials{
Email: req.Email,
Password: req.Password,
})
if err != nil {
return c.JSON(401, map[string]string{"error": "Invalid credentials"})
}
// Generate JWT token
token, err := auth.GenerateToken(c.Context(), auth.TokenClaims{
UserID: user.ID,
Email: user.Email,
Roles: user.Roles,
})
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate token"})
}
return c.JSON(200, map[string]interface{}{
"token": token.AccessToken,
"refresh_token": token.RefreshToken,
"expires_in": token.ExpiresIn,
"user": user,
})
}func registerHandler(c forge.Context) error {
auth := forge.GetAuth(c)
var req struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
Name string `json:"name" validate:"required"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request"})
}
// Validate password policy
if err := auth.ValidatePassword(req.Password); err != nil {
return c.JSON(400, map[string]string{"error": err.Error()})
}
// Create user
user, err := auth.CreateUser(c.Context(), auth.CreateUserRequest{
Email: req.Email,
Password: req.Password,
Name: req.Name,
Roles: []string{"user"}, // Default role
})
if err != nil {
if auth.IsEmailExists(err) {
return c.JSON(409, map[string]string{"error": "Email already exists"})
}
return c.JSON(500, map[string]string{"error": "Failed to create user"})
}
// Send verification email
if err := auth.SendVerificationEmail(c.Context(), user.Email); err != nil {
// Log error but don't fail registration
log.Printf("Failed to send verification email: %v", err)
}
return c.JSON(201, map[string]interface{}{
"user": user,
"message": "User created successfully. Please check your email for verification.",
})
}func forgotPasswordHandler(c forge.Context) error {
auth := forge.GetAuth(c)
var req struct {
Email string `json:"email" validate:"required,email"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request"})
}
// Generate reset token
resetToken, err := auth.GeneratePasswordResetToken(c.Context(), req.Email)
if err != nil {
// Don't reveal if email exists
return c.JSON(200, map[string]string{
"message": "If the email exists, a reset link has been sent.",
})
}
// Send reset email
if err := auth.SendPasswordResetEmail(c.Context(), req.Email, resetToken); err != nil {
log.Printf("Failed to send reset email: %v", err)
}
return c.JSON(200, map[string]string{
"message": "If the email exists, a reset link has been sent.",
})
}
func resetPasswordHandler(c forge.Context) error {
auth := forge.GetAuth(c)
var req struct {
Token string `json:"token" validate:"required"`
Password string `json:"password" validate:"required,min=8"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request"})
}
// Validate password policy
if err := auth.ValidatePassword(req.Password); err != nil {
return c.JSON(400, map[string]string{"error": err.Error()})
}
// Reset password
if err := auth.ResetPassword(c.Context(), req.Token, req.Password); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid or expired token"})
}
return c.JSON(200, map[string]string{
"message": "Password reset successfully",
})
}func logoutHandler(c forge.Context) error {
auth := forge.GetAuth(c)
// Get token from header
token := c.Request().Header.Get("Authorization")
if token == "" {
return c.JSON(400, map[string]string{"error": "No token provided"})
}
// Remove "Bearer " prefix
if len(token) > 7 && token[:7] == "Bearer " {
token = token[7:]
}
// Blacklist token
if err := auth.BlacklistToken(c.Context(), token); err != nil {
return c.JSON(500, map[string]string{"error": "Failed to logout"})
}
return c.JSON(200, map[string]string{
"message": "Logged out successfully",
})
}OAuth2 Authentication
func googleLoginHandler(c forge.Context) error {
auth := forge.GetAuth(c)
// Generate OAuth2 URL
url, state, err := auth.GetOAuth2URL(c.Context(), "google")
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate OAuth URL"})
}
// Store state in session for verification
session := forge.GetSession(c)
session.Set("oauth_state", state)
session.Save()
return c.JSON(200, map[string]string{
"url": url,
})
}
func googleCallbackHandler(c forge.Context) error {
auth := forge.GetAuth(c)
session := forge.GetSession(c)
// Verify state
storedState := session.Get("oauth_state")
if storedState != c.Query("state") {
return c.JSON(400, map[string]string{"error": "Invalid state"})
}
// Exchange code for token
code := c.Query("code")
if code == "" {
return c.JSON(400, map[string]string{"error": "No authorization code"})
}
// Get user info from Google
userInfo, err := auth.ExchangeOAuth2Code(c.Context(), "google", code)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to exchange code"})
}
// Find or create user
user, err := auth.FindOrCreateOAuth2User(c.Context(), auth.OAuth2User{
Provider: "google",
ProviderID: userInfo.ID,
Email: userInfo.Email,
Name: userInfo.Name,
Avatar: userInfo.Picture,
})
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to create user"})
}
// Generate JWT token
token, err := auth.GenerateToken(c.Context(), auth.TokenClaims{
UserID: user.ID,
Email: user.Email,
Roles: user.Roles,
})
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate token"})
}
// Clear OAuth state
session.Delete("oauth_state")
session.Save()
return c.JSON(200, map[string]interface{}{
"token": token.AccessToken,
"refresh_token": token.RefreshToken,
"user": user,
})
}func githubLoginHandler(c forge.Context) error {
auth := forge.GetAuth(c)
url, state, err := auth.GetOAuth2URL(c.Context(), "github")
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate OAuth URL"})
}
session := forge.GetSession(c)
session.Set("oauth_state", state)
session.Save()
return c.JSON(200, map[string]string{
"url": url,
})
}
func githubCallbackHandler(c forge.Context) error {
auth := forge.GetAuth(c)
session := forge.GetSession(c)
// Verify state
storedState := session.Get("oauth_state")
if storedState != c.Query("state") {
return c.JSON(400, map[string]string{"error": "Invalid state"})
}
code := c.Query("code")
if code == "" {
return c.JSON(400, map[string]string{"error": "No authorization code"})
}
// Get user info from GitHub
userInfo, err := auth.ExchangeOAuth2Code(c.Context(), "github", code)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to exchange code"})
}
// GitHub specific: get primary email if not public
if userInfo.Email == "" {
emails, err := auth.GetOAuth2UserEmails(c.Context(), "github", userInfo.AccessToken)
if err == nil && len(emails) > 0 {
for _, email := range emails {
if email.Primary {
userInfo.Email = email.Email
break
}
}
}
}
user, err := auth.FindOrCreateOAuth2User(c.Context(), auth.OAuth2User{
Provider: "github",
ProviderID: userInfo.ID,
Email: userInfo.Email,
Name: userInfo.Name,
Username: userInfo.Login,
Avatar: userInfo.AvatarURL,
})
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to create user"})
}
token, err := auth.GenerateToken(c.Context(), auth.TokenClaims{
UserID: user.ID,
Email: user.Email,
Roles: user.Roles,
})
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate token"})
}
session.Delete("oauth_state")
session.Save()
return c.JSON(200, map[string]interface{}{
"token": token.AccessToken,
"refresh_token": token.RefreshToken,
"user": user,
})
}func oauthLoginHandler(c forge.Context) error {
auth := forge.GetAuth(c)
provider := c.Param("provider")
// Validate provider
if !auth.IsProviderSupported(provider) {
return c.JSON(400, map[string]string{"error": "Unsupported provider"})
}
url, state, err := auth.GetOAuth2URL(c.Context(), provider)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate OAuth URL"})
}
session := forge.GetSession(c)
session.Set("oauth_state", state)
session.Set("oauth_provider", provider)
session.Save()
return c.JSON(200, map[string]string{
"url": url,
"provider": provider,
})
}
func oauthCallbackHandler(c forge.Context) error {
auth := forge.GetAuth(c)
session := forge.GetSession(c)
provider := session.Get("oauth_provider").(string)
storedState := session.Get("oauth_state")
if storedState != c.Query("state") {
return c.JSON(400, map[string]string{"error": "Invalid state"})
}
code := c.Query("code")
if code == "" {
return c.JSON(400, map[string]string{"error": "No authorization code"})
}
userInfo, err := auth.ExchangeOAuth2Code(c.Context(), provider, code)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to exchange code"})
}
user, err := auth.FindOrCreateOAuth2User(c.Context(), auth.OAuth2User{
Provider: provider,
ProviderID: userInfo.ID,
Email: userInfo.Email,
Name: userInfo.Name,
Avatar: userInfo.Picture,
})
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to create user"})
}
token, err := auth.GenerateToken(c.Context(), auth.TokenClaims{
UserID: user.ID,
Email: user.Email,
Roles: user.Roles,
})
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate token"})
}
session.Delete("oauth_state")
session.Delete("oauth_provider")
session.Save()
return c.JSON(200, map[string]interface{}{
"token": token.AccessToken,
"refresh_token": token.RefreshToken,
"user": user,
"provider": provider,
})
}Multi-Factor Authentication
func setupTOTPHandler(c forge.Context) error {
auth := forge.GetAuth(c)
userID := c.Get("user_id").(string)
// Generate TOTP secret
secret, qrCode, err := auth.GenerateTOTPSecret(c.Context(), userID)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate TOTP secret"})
}
return c.JSON(200, map[string]interface{}{
"secret": secret,
"qr_code": qrCode,
"manual_entry_key": secret,
})
}
func confirmTOTPHandler(c forge.Context) error {
auth := forge.GetAuth(c)
userID := c.Get("user_id").(string)
var req struct {
Code string `json:"code" validate:"required,len=6"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request"})
}
// Verify TOTP code
valid, err := auth.VerifyTOTP(c.Context(), userID, req.Code)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to verify TOTP"})
}
if !valid {
return c.JSON(400, map[string]string{"error": "Invalid TOTP code"})
}
// Enable TOTP for user
if err := auth.EnableTOTP(c.Context(), userID); err != nil {
return c.JSON(500, map[string]string{"error": "Failed to enable TOTP"})
}
// Generate backup codes
backupCodes, err := auth.GenerateBackupCodes(c.Context(), userID)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate backup codes"})
}
return c.JSON(200, map[string]interface{}{
"message": "TOTP enabled successfully",
"backup_codes": backupCodes,
})
}func verifyMFAHandler(c forge.Context) error {
auth := forge.GetAuth(c)
var req struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
TOTPCode string `json:"totp_code"`
BackupCode string `json:"backup_code"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request"})
}
// First, authenticate with email/password
user, err := auth.Authenticate(c.Context(), auth.Credentials{
Email: req.Email,
Password: req.Password,
})
if err != nil {
return c.JSON(401, map[string]string{"error": "Invalid credentials"})
}
// Check if MFA is required
if !user.MFAEnabled {
// Generate token directly
token, err := auth.GenerateToken(c.Context(), auth.TokenClaims{
UserID: user.ID,
Email: user.Email,
Roles: user.Roles,
})
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate token"})
}
return c.JSON(200, map[string]interface{}{
"token": token.AccessToken,
"refresh_token": token.RefreshToken,
"user": user,
})
}
// Verify MFA
var mfaValid bool
if req.TOTPCode != "" {
mfaValid, err = auth.VerifyTOTP(c.Context(), user.ID, req.TOTPCode)
} else if req.BackupCode != "" {
mfaValid, err = auth.VerifyBackupCode(c.Context(), user.ID, req.BackupCode)
} else {
return c.JSON(400, map[string]string{"error": "MFA code required"})
}
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to verify MFA"})
}
if !mfaValid {
return c.JSON(400, map[string]string{"error": "Invalid MFA code"})
}
// Generate token
token, err := auth.GenerateToken(c.Context(), auth.TokenClaims{
UserID: user.ID,
Email: user.Email,
Roles: user.Roles,
})
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate token"})
}
return c.JSON(200, map[string]interface{}{
"token": token.AccessToken,
"refresh_token": token.RefreshToken,
"user": user,
})
}func sendSMSCodeHandler(c forge.Context) error {
auth := forge.GetAuth(c)
userID := c.Get("user_id").(string)
// Get user's phone number
user, err := auth.GetUser(c.Context(), userID)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to get user"})
}
if user.PhoneNumber == "" {
return c.JSON(400, map[string]string{"error": "No phone number registered"})
}
// Generate and send SMS code
code, err := auth.SendSMSCode(c.Context(), user.PhoneNumber)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to send SMS code"})
}
return c.JSON(200, map[string]interface{}{
"message": "SMS code sent",
"phone_number": maskPhoneNumber(user.PhoneNumber),
})
}
func verifySMSCodeHandler(c forge.Context) error {
auth := forge.GetAuth(c)
userID := c.Get("user_id").(string)
var req struct {
Code string `json:"code" validate:"required,len=6"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request"})
}
// Verify SMS code
valid, err := auth.VerifySMSCode(c.Context(), userID, req.Code)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to verify SMS code"})
}
if !valid {
return c.JSON(400, map[string]string{"error": "Invalid SMS code"})
}
return c.JSON(200, map[string]string{
"message": "SMS code verified successfully",
})
}
func maskPhoneNumber(phone string) string {
if len(phone) < 4 {
return phone
}
return phone[:len(phone)-4] + "****"
}func generateBackupCodesHandler(c forge.Context) error {
auth := forge.GetAuth(c)
userID := c.Get("user_id").(string)
// Verify current password for security
var req struct {
Password string `json:"password" validate:"required"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request"})
}
// Get user and verify password
user, err := auth.GetUser(c.Context(), userID)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to get user"})
}
if !auth.VerifyPassword(req.Password, user.PasswordHash) {
return c.JSON(401, map[string]string{"error": "Invalid password"})
}
// Generate new backup codes
backupCodes, err := auth.GenerateBackupCodes(c.Context(), userID)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to generate backup codes"})
}
return c.JSON(200, map[string]interface{}{
"backup_codes": backupCodes,
"message": "New backup codes generated. Store them securely.",
})
}
func viewBackupCodesHandler(c forge.Context) error {
auth := forge.GetAuth(c)
userID := c.Get("user_id").(string)
// Get remaining backup codes
codes, err := auth.GetBackupCodes(c.Context(), userID)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to get backup codes"})
}
// Mask codes for security (show only first 2 characters)
maskedCodes := make([]string, len(codes))
for i, code := range codes {
if len(code) > 2 {
maskedCodes[i] = code[:2] + "******"
} else {
maskedCodes[i] = "******"
}
}
return c.JSON(200, map[string]interface{}{
"backup_codes": maskedCodes,
"count": len(codes),
})
}Middleware and Authorization
func setupAuthMiddleware(app *forge.App) {
auth := forge.GetAuth(app)
// JWT Authentication middleware
jwtMiddleware := auth.JWTMiddleware(auth.JWTMiddlewareConfig{
SkipPaths: []string{
"/auth/login",
"/auth/register",
"/auth/forgot-password",
"/health",
"/metrics",
},
ErrorHandler: func(c forge.Context, err error) error {
return c.JSON(401, map[string]string{
"error": "Unauthorized",
"message": err.Error(),
})
},
})
// Apply middleware to protected routes
protected := app.Group("/api", jwtMiddleware)
// User routes
protected.GET("/profile", getProfileHandler)
protected.PUT("/profile", updateProfileHandler)
// Admin routes with role check
admin := protected.Group("/admin", auth.RequireRole("admin"))
admin.GET("/users", listUsersHandler)
admin.DELETE("/users/:id", deleteUserHandler)
}
func getProfileHandler(c forge.Context) error {
// User is already authenticated by middleware
userID := c.Get("user_id").(string)
auth := forge.GetAuth(c)
user, err := auth.GetUser(c.Context(), userID)
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to get user"})
}
return c.JSON(200, user)
}func setupRoleBasedRoutes(app *forge.App) {
auth := forge.GetAuth(app)
// Different access levels
api := app.Group("/api", auth.JWTMiddleware())
// User level access
user := api.Group("/user", auth.RequireRole("user"))
user.GET("/profile", getUserProfileHandler)
user.PUT("/profile", updateUserProfileHandler)
// Manager level access
manager := api.Group("/manager", auth.RequireRole("manager"))
manager.GET("/team", getTeamHandler)
manager.POST("/team/invite", inviteTeamMemberHandler)
// Admin level access
admin := api.Group("/admin", auth.RequireRole("admin"))
admin.GET("/users", listAllUsersHandler)
admin.DELETE("/users/:id", deleteUserHandler)
admin.POST("/roles", createRoleHandler)
// Super admin access
superAdmin := api.Group("/super-admin", auth.RequireRole("super_admin"))
superAdmin.GET("/system", getSystemInfoHandler)
superAdmin.POST("/maintenance", enableMaintenanceModeHandler)
}
func createRoleHandler(c forge.Context) error {
auth := forge.GetAuth(c)
var req struct {
Name string `json:"name" validate:"required"`
Description string `json:"description"`
Permissions []string `json:"permissions" validate:"required"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request"})
}
role, err := auth.CreateRole(c.Context(), auth.Role{
Name: req.Name,
Description: req.Description,
Permissions: req.Permissions,
})
if err != nil {
return c.JSON(500, map[string]string{"error": "Failed to create role"})
}
return c.JSON(201, role)
}func setupPermissionBasedRoutes(app *forge.App) {
auth := forge.GetAuth(app)
api := app.Group("/api", auth.JWTMiddleware())
// Resource-specific permissions
api.GET("/posts", auth.RequirePermission("read:posts"), listPostsHandler)
api.POST("/posts", auth.RequirePermission("create:posts"), createPostHandler)
api.PUT("/posts/:id", auth.RequirePermission("update:posts"), updatePostHandler)
api.DELETE("/posts/:id", auth.RequirePermission("delete:posts"), deletePostHandler)
// Multiple permissions (user needs ALL)
api.GET("/admin/reports",
auth.RequirePermissions([]string{"read:reports", "access:admin"}),
getReportsHandler)
// Any permission (user needs ANY)
api.GET("/content",
auth.RequireAnyPermission([]string{"read:posts", "read:pages"}),
getContentHandler)
}
func updatePostHandler(c forge.Context) error {
auth := forge.GetAuth(c)
userID := c.Get("user_id").(string)
postID := c.Param("id")
// Check if user owns the post or has admin permission
post, err := getPost(postID)
if err != nil {
return c.JSON(404, map[string]string{"error": "Post not found"})
}
if post.AuthorID != userID {
// Check if user has admin permission to edit any post
hasPermission, err := auth.HasPermission(c.Context(), userID, "admin:posts")
if err != nil || !hasPermission {
return c.JSON(403, map[string]string{"error": "Insufficient permissions"})
}
}
// Update post logic here
return c.JSON(200, map[string]string{"message": "Post updated"})
}
func customPermissionMiddleware(permission string) forge.MiddlewareFunc {
return func(next forge.HandlerFunc) forge.HandlerFunc {
return func(c forge.Context) error {
auth := forge.GetAuth(c)
userID := c.Get("user_id").(string)
hasPermission, err := auth.HasPermission(c.Context(), userID, permission)
if err != nil {
return c.JSON(500, map[string]string{"error": "Permission check failed"})
}
if !hasPermission {
return c.JSON(403, map[string]string{"error": "Insufficient permissions"})
}
return next(c)
}
}
}func setupRateLimiting(app *forge.App) {
auth := forge.GetAuth(app)
// Global rate limiting
app.Use(auth.RateLimitMiddleware(auth.RateLimitConfig{
MaxRequests: 100,
Window: time.Minute,
KeyFunc: func(c forge.Context) string {
// Rate limit by IP
return c.RealIP()
},
}))
// Authentication specific rate limiting
authGroup := app.Group("/auth")
// Stricter rate limiting for login attempts
authGroup.Use(auth.RateLimitMiddleware(auth.RateLimitConfig{
MaxRequests: 5,
Window: time.Minute * 15,
KeyFunc: func(c forge.Context) string {
// Rate limit by IP for auth endpoints
return "auth:" + c.RealIP()
},
OnLimitReached: func(c forge.Context) error {
return c.JSON(429, map[string]interface{}{
"error": "Too many authentication attempts",
"retry_after": 900, // 15 minutes
})
},
}))
authGroup.POST("/login", loginHandler)
authGroup.POST("/register", registerHandler)
authGroup.POST("/forgot-password", forgotPasswordHandler)
// User-specific rate limiting for authenticated routes
api := app.Group("/api", auth.JWTMiddleware())
api.Use(auth.RateLimitMiddleware(auth.RateLimitConfig{
MaxRequests: 1000,
Window: time.Hour,
KeyFunc: func(c forge.Context) string {
// Rate limit by user ID
userID := c.Get("user_id").(string)
return "user:" + userID
},
}))
}
func setupAdvancedRateLimiting(app *forge.App) {
auth := forge.GetAuth(app)
// Different limits for different user roles
api := app.Group("/api", auth.JWTMiddleware())
api.Use(func(next forge.HandlerFunc) forge.HandlerFunc {
return func(c forge.Context) error {
userID := c.Get("user_id").(string)
user, err := auth.GetUser(c.Context(), userID)
if err != nil {
return next(c)
}
var maxRequests int
var window time.Duration
// Set limits based on user role
switch {
case contains(user.Roles, "premium"):
maxRequests = 10000
window = time.Hour
case contains(user.Roles, "admin"):
maxRequests = 50000
window = time.Hour
default:
maxRequests = 1000
window = time.Hour
}
// Apply rate limiting
rateLimiter := auth.RateLimitMiddleware(auth.RateLimitConfig{
MaxRequests: maxRequests,
Window: window,
KeyFunc: func(c forge.Context) string {
return "user:" + userID
},
})
return rateLimiter(next)(c)
}
})
}
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}Advanced Features
Custom Authentication Provider
// Implement custom authentication provider
type CustomAuthProvider struct {
config CustomAuthConfig
}
func (p *CustomAuthProvider) Name() string {
return "custom"
}
func (p *CustomAuthProvider) Type() auth.SecuritySchemeType {
return auth.SecuritySchemeTypeAPIKey
}
func (p *CustomAuthProvider) Authenticate(ctx context.Context, credentials auth.Credentials) (*auth.User, error) {
// Custom authentication logic
apiKey := credentials.APIKey
// Validate API key against your system
user, err := p.validateAPIKey(ctx, apiKey)
if err != nil {
return nil, auth.ErrInvalidCredentials
}
return user, nil
}
func (p *CustomAuthProvider) Middleware() forge.MiddlewareFunc {
return func(next forge.HandlerFunc) forge.HandlerFunc {
return func(c forge.Context) error {
apiKey := c.Request().Header.Get("X-API-Key")
if apiKey == "" {
return c.JSON(401, map[string]string{"error": "API key required"})
}
user, err := p.Authenticate(c.Context(), auth.Credentials{APIKey: apiKey})
if err != nil {
return c.JSON(401, map[string]string{"error": "Invalid API key"})
}
c.Set("user", user)
c.Set("user_id", user.ID)
return next(c)
}
}
}
// Register custom provider
func setupCustomAuth(app *forge.App) {
auth := forge.GetAuth(app)
customProvider := &CustomAuthProvider{
config: CustomAuthConfig{
// Your custom config
},
}
auth.RegisterProvider(customProvider)
}Session Management
func setupSessionManagement(app *forge.App) {
auth := forge.GetAuth(app)
// Session middleware
app.Use(auth.SessionMiddleware(auth.SessionConfig{
Store: "redis", // or "memory", "database"
Secure: true,
HTTPOnly: true,
SameSite: "strict",
MaxAge: time.Hour * 24,
}))
// Login with session
app.POST("/auth/session/login", func(c forge.Context) error {
var req struct {
Email string `json:"email"`
Password string `json:"password"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(400, map[string]string{"error": "Invalid request"})
}
user, err := auth.Authenticate(c.Context(), auth.Credentials{
Email: req.Email,
Password: req.Password,
})
if err != nil {
return c.JSON(401, map[string]string{"error": "Invalid credentials"})
}
// Create session
session := forge.GetSession(c)
session.Set("user_id", user.ID)
session.Set("authenticated", true)
session.Save()
return c.JSON(200, map[string]interface{}{
"message": "Logged in successfully",
"user": user,
})
})
// Session-based authentication middleware
sessionAuth := func(next forge.HandlerFunc) forge.HandlerFunc {
return func(c forge.Context) error {
session := forge.GetSession(c)
userID := session.Get("user_id")
authenticated := session.Get("authenticated")
if userID == nil || authenticated != true {
return c.JSON(401, map[string]string{"error": "Not authenticated"})
}
c.Set("user_id", userID)
return next(c)
}
}
// Protected routes with session auth
protected := app.Group("/api", sessionAuth)
protected.GET("/profile", getProfileHandler)
}Best Practices
Security
- Always use HTTPS in production
- Store secrets securely (environment variables, secret managers)
- Implement proper password policies
- Use secure session configurations
- Enable MFA for sensitive accounts
- Regularly rotate secrets and tokens
Performance
- Use caching for user data and permissions
- Implement efficient token validation
- Use connection pooling for external providers
- Monitor authentication performance
Error Handling
- Don't reveal sensitive information in error messages
- Log authentication events for security monitoring
- Implement proper rate limiting
- Handle edge cases gracefully
Monitoring
- Track authentication metrics
- Monitor failed login attempts
- Set up alerts for suspicious activity
- Audit user permissions regularly
Troubleshooting
Common Issues
JWT Token Issues
// Validate JWT configuration
config := auth.GetJWTConfig()
if config.Secret == "" {
log.Fatal("JWT secret not configured")
}
// Check token expiry
token, err := auth.ParseToken(tokenString)
if err != nil {
if auth.IsTokenExpired(err) {
// Handle expired token
return refreshToken(c)
}
return c.JSON(401, map[string]string{"error": "Invalid token"})
}OAuth2 Configuration
// Verify OAuth2 provider setup
providers := auth.GetOAuth2Providers()
for name, provider := range providers {
if provider.ClientID == "" || provider.ClientSecret == "" {
log.Printf("OAuth2 provider %s not properly configured", name)
}
}Rate Limiting Issues
// Check rate limit status
status, err := auth.GetRateLimitStatus(c.Context(), "user:123")
if err != nil {
log.Printf("Rate limit check failed: %v", err)
}
log.Printf("Rate limit: %d/%d requests, resets at %v",
status.Current, status.Limit, status.ResetTime)Always test authentication flows thoroughly in a staging environment before deploying to production.
Next Steps
Setup: Configure your authentication providers and security settings
Integration: Implement authentication in your application routes
Security: Enable MFA and implement proper security measures
Monitoring: Set up authentication monitoring and alerting
Optimization: Optimize performance and implement caching
How is this guide?
Last updated on