Hooks
Configure hooks to customise the HTTP request/response lifecycle in Authula.
Overview
The hooks system in Authula provides a flexible and extensible mechanism for plugins to intercept and modify the request lifecycle. Hooks allow you to execute custom logic at specific points during request processing without tightly coupling your code to the router.
The system enables powerful capabilities such as authentication, authorization, rate limiting, logging, and custom business logic integration. Each hook operates within the request context and can modify request state, share data with other hooks, or short-circuit the request flow.
Hook Stages
Hooks execute at four distinct stages in the request lifecycle:
1. HookOnRequest
- Executes for every incoming request at the very start
- Ideal for global logging, metrics collection, or request preprocessing
- Runs before route matching occurs
2. HookBefore
- Executes after route matching but before the main route handler
- Perfect for authentication, authorization, and request validation
- Common use: checking session tokens, validating permissions
3. HookAfter
- Executes after the route handler but before the response is sent
- Useful for response modification, post-processing, or cleanup
- Good for adding response headers or transforming output
4. HookOnResponse
- Executes after the response has been written to the client
- Suitable for analytics, audit logging, or fire-and-forget operations
- Does not affect the response sent to the client
Configuration Options
Synchronization Types
Hooks can be either synchronous or asynchronous:
// Synchronous hook (default)
Hook{
Async: false, // Blocks the request flow until completion
Handler: myHandler,
}
// Asynchronous hook
Hook{
Async: true, // Runs in a background goroutine
Handler: myAsyncHandler,
}- Synchronous: Blocks the request flow and waits for completion. Use for authentication, validation, and critical security checks.
- Asynchronous: Runs in a background goroutine without blocking the response. Ideal for logging, analytics, and side effects.
Error Handling Modes
Configure how the router handles hook errors:
opts := &RouterOptions{
HookErrorMode: HookErrorModeContinue, // Default: log errors, continue execution
}HookErrorModeContinue(default): Logs errors but continues to the next hookHookErrorModeFailFast: Logs error and stops remaining hooks in the current stageHookErrorModeSilent: Silently ignores errors without logging
Async Hook Timeout
Control how long async hooks can run before being cancelled:
opts := &RouterOptions{
AsyncHookTimeout: 30 * time.Second, // Default timeout
}Hook Structure
Each hook is defined with the following properties:
type Hook struct {
Stage HookStage // When to execute (HookOnRequest, HookBefore, etc.)
PluginID string // Optional: limits execution to specific routes
Matcher HookMatcher // Optional: conditional execution based on request
Handler HookHandler // The function that executes the hook
Order int // Execution order within the same stage
Async bool // Whether to run asynchronously
}Properties Explained
- Stage: Determines when the hook executes in the request lifecycle
- PluginID: If set, hook only executes if the route's metadata includes this plugin ID
- Matcher: A function that returns true/false to conditionally execute the hook
- Handler: The actual function that performs the hook's logic
- Order: Controls execution order among hooks in the same stage (lower numbers first)
- Async: Whether the hook runs synchronously or asynchronously
Practical Examples
Basic Authentication Hook
// Simple authentication hook that checks for a valid session
authHook := models.Hook{
Stage: models.HookBefore,
Order: 0,
Handler: func(ctx *models.RequestContext) error {
// Check if user is authenticated
sessionToken := ctx.Headers.Get("Authorization")
if sessionToken == "" {
ctx.SetResponse(401, nil, []byte(`{"error": "Unauthorized"}`))
ctx.Handled = true
return nil
}
// Validate session and set user info
userID, err := validateSession(sessionToken)
if err != nil {
ctx.SetResponse(401, nil, []byte(`{"error": "Invalid session"}`))
ctx.Handled = true
return nil
}
// Store user ID in context for later use
ctx.SetUserIDInContext(userID)
return nil
},
}Conditional Hook with Matcher
// Only execute hook for admin routes
adminOnlyHook := models.Hook{
Stage: models.HookBefore,
Matcher: func(ctx *models.RequestContext) bool {
// Only run for paths starting with /admin
return strings.HasPrefix(ctx.Path, "/admin")
},
Handler: func(ctx *models.RequestContext) error {
userID := ctx.UserID
if userID == nil {
ctx.SetResponse(401, nil, []byte(`{"error": "Authentication required"}`))
ctx.Handled = true
return nil
}
// Check if user has admin role
isAdmin, err := userHasRole(*userID, "admin")
if err != nil || !isAdmin {
ctx.SetResponse(403, nil, []byte(`{"error": "Admin access required"}`))
ctx.Handled = true
return nil
}
return nil
},
}Data Sharing Between Hooks
// First hook sets data in the Values map
setUserDataHook := models.Hook{
Stage: models.HookBefore,
Order: 0,
Handler: func(ctx *models.RequestContext) error {
userID := ctx.UserID
if userID != nil {
// Fetch user profile and store in Values for other hooks
userProfile, err := getUserProfile(*userID)
if err != nil {
return err
}
ctx.Values["user_profile"] = userProfile
}
return nil
},
}
// Second hook reads the shared data
auditLogHook := models.Hook{
Stage: models.HookAfter,
Order: 1, // Must run after setUserDataHook
Handler: func(ctx *models.RequestContext) error {
if profile, exists := ctx.Values["user_profile"]; exists {
// Log the action with user information
logAuditEvent(profile.(UserProfile), ctx.Path, ctx.Method)
}
return nil
},
}Asynchronous Analytics Hook
// Fire-and-forget analytics hook
analyticsHook := models.Hook{
Stage: models.HookOnResponse,
Async: true,
Handler: func(ctx *models.RequestContext) error {
// Track page views, user actions, etc.
trackEvent("page_view", map[string]interface{}{
"path": ctx.Path,
"method": ctx.Method,
"clientIP": ctx.ClientIP,
"userID": ctx.UserID,
})
return nil
},
}Plugin-Specific Hook
// Hook that only runs for routes that include this plugin
rateLimitHook := models.Hook{
Stage: models.HookBefore,
PluginID: "ratelimit",
Order: 0,
Handler: func(ctx *models.RequestContext) error {
// Apply rate limiting only to routes that have "ratelimit" in their metadata
if err := checkRateLimit(ctx.ClientIP); err != nil {
ctx.SetResponse(429, nil, []byte(`{"error": "Rate limit exceeded"}`))
ctx.Handled = true
return nil
}
return nil
},
}Best Practices
1. Order Matters
- Use lower
Ordervalues for security-critical hooks (authentication, authorization) - Higher
Ordervalues for less critical operations - Remember that order is local to each plugin, so compare within the same
PluginID
2. Handle Errors Gracefully
- Always return appropriate errors from your handlers
- Use
ctx.Handled = trueto short-circuit when returning an error response - Consider the error handling mode when designing your hooks
3. Use Asynchronous Hooks for Side Effects
- Use
Async: truefor logging, analytics, notifications - Keep synchronous hooks for authentication, validation, and response modification
- Be mindful of the timeout for async hooks
4. Leverage the Values Map
- Use
ctx.Valuesto share data between hooks - Store computed values that other hooks might need
- Avoid storing sensitive information in Values
5. Use Matchers for Conditional Logic
- Implement
Matcherfunctions to conditionally execute hooks - This is more efficient than checking conditions inside the handler
- Keep matchers lightweight to avoid performance overhead
6. Plugin Integration
- Use
PluginIDto ensure hooks only run on relevant routes - Coordinate with route metadata to enable/disable hooks per endpoint
- Follow the plugin architecture patterns for consistency
Use Cases
Authentication & Authorization
- Session validation
- JWT verification
- Role-based access control
- Permission checking
Security
- Rate limiting
- CSRF protection
- Input validation
- IP blacklisting
Monitoring & Analytics
- Request logging
- Performance metrics
- User activity tracking
- Error monitoring
Business Logic
- Custom validation rules
- Data enrichment
- Audit trails
- Notification triggers
The hooks system provides a powerful and flexible foundation for extending Authula's functionality while maintaining clean separation of concerns between different aspects of your authentication and authorization logic.
NOTE: this documentation explains a general overview of the hooks system but bear in mind that Authula may already contain a lot of this functionality already as plugins so you may not need to implement the things that are mentioned under "use cases" in your own project etc. So take a look at the Plugins documentation next to see what is already available!
