Context Helpers¶
The Context Helpers package provides type-safe, reusable functions for extracting user authentication information from Gin request contexts. This eliminates repetitive boilerplate code in handlers and provides a clean, maintainable approach to user authentication and authorization.
๐ฏ Overview¶
The Context Helpers package (internal/ctx
) offers a comprehensive set of functions that simplify user authentication and authorization in your handlers. Instead of manually extracting and validating JWT claims in every protected endpoint, you can use these helper functions for clean, readable, and maintainable code.
Key Benefits¶
- ๐ DRY Principle: Eliminates code duplication across handlers
- ๐ก๏ธ Type Safety: Built-in type assertions with proper error handling
- ๐งช Testability: Comprehensive test coverage ensures reliability
- ๐ Readability: Clean, self-documenting authentication code
- โก Performance: Optimized context extraction with minimal overhead
๐ฆ Available Functions¶
User Information Extraction¶
GetUser(c *gin.Context) *auth.Claims
¶
Retrieves the complete authenticated user claims from context.
claims := ctx.GetUser(c)
if claims != nil {
// User is authenticated
fmt.Printf("User ID: %d, Email: %s\n", claims.UserID, claims.Email)
}
MustGetUser(c *gin.Context) (*auth.Claims, error)
¶
Retrieves user claims or returns an error if not found.
claims, err := ctx.MustGetUser(c)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
// Use claims safely
GetUserID(c *gin.Context) uint
¶
Extracts the authenticated user's ID from context. Returns 0
if not found.
userID := ctx.GetUserID(c)
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
MustGetUserID(c *gin.Context) (uint, error)
¶
Gets user ID with error handling.
userID, err := ctx.MustGetUserID(c)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
GetEmail(c *gin.Context) string
¶
Extracts the authenticated user's email address.
email := ctx.GetEmail(c)
if email == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
GetUserName(c *gin.Context) string
¶
Extracts the authenticated user's name.
Authentication & Authorization¶
IsAuthenticated(c *gin.Context) bool
¶
Checks if the request has valid authentication.
if !ctx.IsAuthenticated(c) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
CanAccessUser(c *gin.Context, targetUserID uint) bool
¶
Checks if the authenticated user can access the target user's resources (ownership-based access control).
if !ctx.CanAccessUser(c, uint(id)) {
c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
return
}
HasRole(c *gin.Context, role string) bool
¶
Checks if the user has a specific role (placeholder for future RBAC implementation).
if !ctx.HasRole(c, "admin") {
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
return
}
Future RBAC Support
The HasRole
function is currently a placeholder that returns false
. It will be implemented when role-based access control is added to the system.
๐ Code Transformation¶
Before: Repetitive Boilerplate¶
Every protected handler required verbose authentication code:
func (h *Handler) GetUser(c *gin.Context) {
// Parse ID from URL
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
// Extract user claims manually
claims, exists := c.Get("user")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
userClaims := claims.(*auth.Claims)
// Authorization check
if userClaims.UserID != uint(id) {
c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
return
}
// Actual handler logic...
}
After: Clean & Type-Safe¶
With Context Helpers, the same functionality becomes clean and readable:
func (h *Handler) GetUser(c *gin.Context) {
// Parse ID from URL
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
// Clean authorization check
if !ctx.CanAccessUser(c, uint(id)) {
c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
return
}
// Actual handler logic...
}
๐ Impact Metrics¶
Metric | Before | After | Improvement |
---|---|---|---|
Lines per handler | 5-6 lines | 1 line | 83% reduction |
Type safety | Manual assertions | Built-in | 100% safe |
Test coverage | None | 25+ tests | Complete coverage |
Code duplication | High | Eliminated | DRY principle |
๐ ๏ธ Usage Examples¶
Basic Authentication Check¶
func (h *Handler) ProtectedEndpoint(c *gin.Context) {
if !ctx.IsAuthenticated(c) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
return
}
userID := ctx.GetUserID(c)
// Use userID safely
}
Resource Ownership Validation¶
func (h *Handler) UpdateUser(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
// Check if user can access this resource
if !ctx.CanAccessUser(c, uint(id)) {
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
return
}
// Proceed with update...
}
User Information Display¶
func (h *Handler) GetProfile(c *gin.Context) {
user := ctx.GetUser(c)
if user == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
profile := gin.H{
"id": user.UserID,
"email": user.Email,
"name": user.Name,
}
c.JSON(http.StatusOK, profile)
}
Error Handling with Must Functions¶
func (h *Handler) StrictEndpoint(c *gin.Context) {
userID, err := ctx.MustGetUserID(c)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
return
}
email, err := ctx.MustGetUser(c)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User information missing"})
return
}
// Use userID and email safely
}
๐งช Testing¶
The Context Helpers package includes comprehensive test coverage with 25+ unit tests covering:
- โ Happy path scenarios: Valid user extraction
- โ Edge cases: Missing authentication, invalid types
- โ Error conditions: Malformed context data
- โ Authorization logic: Access control validation
- โ Type safety: Proper type assertions
Running Tests¶
# Run all tests
go test ./tests/context_test.go -v
# Run with coverage
go test ./tests/context_test.go -v -cover
# Run all project tests
make test
๐ง Integration¶
Prerequisites¶
The Context Helpers require:
- JWT Authentication Middleware: Must be applied to protected routes
- Auth Claims Structure: Compatible with
internal/auth.Claims
- Context Key: Uses
auth.KeyUser
constant for context storage
Middleware Setup¶
Ensure your routes are protected with the authentication middleware:
// In your router setup
authMiddleware := auth.NewMiddleware(authService)
protected := router.Group("/api/v1")
protected.Use(authMiddleware)
// Now handlers can use context helpers
protected.GET("/users/:id", userHandler.GetUser)
๐ Best Practices¶
1. Use Appropriate Functions¶
- Safe functions (
GetUserID
,GetEmail
) for optional user info - Must functions (
MustGetUserID
,MustGetUser
) when authentication is required - Boolean checks (
IsAuthenticated
,CanAccessUser
) for conditional logic
2. Consistent Error Handling¶
// Good: Consistent error responses
if !ctx.IsAuthenticated(c) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
// Good: Use appropriate HTTP status codes
if !ctx.CanAccessUser(c, targetID) {
c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
return
}
3. Combine with Validation¶
func (h *Handler) UpdateUser(c *gin.Context) {
// Input validation
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
// Authentication & authorization
if !ctx.CanAccessUser(c, uint(id)) {
c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
return
}
// Business logic validation
var req UpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Proceed with update...
}
๐ฎ Future Enhancements¶
The Context Helpers package is designed for extensibility:
- Role-Based Access Control (RBAC):
HasRole
function ready for implementation - Permission System: Extensible for complex permission checks
- Multi-tenant Support: Ready for tenant-based access control
- Audit Logging: Easy integration with user context information
๐ Related Documentation¶
- Authentication Guide - JWT authentication setup
- Middleware Documentation - Request processing pipeline
- Testing Guide - Comprehensive testing strategies
- API Documentation - Complete API reference
The Context Helpers package transforms authentication code from repetitive boilerplate into clean, maintainable, and testable functions. By eliminating code duplication and providing type-safe user extraction, it significantly improves developer experience and code quality.