auth(JWT authentication)

A comprehensive JWT authentication system supporting both Gin and gRPC contexts with flexible token extraction and validation.

The auth module provides a comprehensive JWT authentication system with support for both Gin and gRPC contexts, flexible token extraction methods, and robust validation mechanisms.

Features

  • Multiple signing algorithms: HS256, HS384, HS512, RS256, RS384, RS512
  • Flexible token extraction from headers, cookies, query parameters, form data, and URL parameters
  • Context-aware parsing for both Gin and gRPC frameworks
  • Token refresh mechanism with configurable expiration
  • RSA key support for asymmetric algorithms
  • Custom payload handling with user-defined claims
  • Comprehensive error handling with specific error types

Basic JWT Setup

 1package main
 2
 3import (
 4    "time"
 5    
 6    "github.com/crazyfrankie/frx/auth/authn"
 7)
 8
 9func main() {
10    cfg := &authn.Config{
11        Realm:            "MyApp",
12        SecretKey:        []byte("your-secret-key-here"),
13        SigningAlgorithm: "HS256",
14        Timeout:          time.Hour * 24,
15        MaxRefresh:       time.Hour * 24 * 7,
16        TokenLookup:      "header:Authorization",
17        TokenHeadName:    "Bearer",
18        PayloadFunc: func(data interface{}) authn.MapClaims {
19            if user, ok := data.(*User); ok {
20                return authn.MapClaims{
21                    "user_id": user.ID,
22                    "username": user.Username,
23                    "role": user.Role,
24                }
25            }
26            return authn.MapClaims{}
27        },
28    }
29    
30    jwtHandler, err := authn.New(cfg)
31    if err != nil {
32        panic(err)
33    }
34}

Token Generation

 1type User struct {
 2    ID       int64  `json:"id"`
 3    Username string `json:"username"`
 4    Role     string `json:"role"`
 5}
 6
 7func GenerateUserToken(jwtHandler *authn.JWTHandler, user *User) (string, error) {
 8    token, err := jwtHandler.GenerateToken(user)
 9    if err != nil {
10        return "", err
11    }
12    
13    return token, nil
14}

Gin Integration

 1func AuthMiddleware(jwtHandler *authn.JWTHandler) gin.HandlerFunc {
 2    return func(c *gin.Context) {
 3        token, err := jwtHandler.ParseToken(c)
 4        if err != nil {
 5            c.JSON(401, gin.H{"error": "Invalid token"})
 6            c.Abort()
 7            return
 8        }
 9        
10        if !token.Valid {
11            c.JSON(401, gin.H{"error": "Token is not valid"})
12            c.Abort()
13            return
14        }
15        
16        claims := token.Claims.(jwt.MapClaims)
17        c.Set("user_id", claims["user_id"])
18        c.Set("username", claims["username"])
19        c.Set("role", claims["role"])
20        
21        c.Next()
22    }
23}
24
25func SetupRoutes(jwtHandler *authn.JWTHandler) *gin.Engine {
26    r := gin.Default()
27    
28    // Public routes
29    r.POST("/login", func(c *gin.Context) {
30        // Login logic here
31        user := &User{ID: 1, Username: "john", Role: "admin"}
32        token, err := jwtHandler.GenerateToken(user)
33        if err != nil {
34            c.JSON(500, gin.H{"error": "Failed to generate token"})
35            return
36        }
37        
38        c.JSON(200, gin.H{"token": token})
39    })
40    
41    // Protected routes
42    protected := r.Group("/api")
43    protected.Use(AuthMiddleware(jwtHandler))
44    {
45        protected.GET("/profile", func(c *gin.Context) {
46            userID := c.GetString("user_id")
47            c.JSON(200, gin.H{"user_id": userID})
48        })
49    }
50    
51    return r
52}

gRPC Integration

 1func GRPCAuthInterceptor(jwtHandler *authn.JWTHandler) grpc.UnaryServerInterceptor {
 2    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
 3        token, err := jwtHandler.ParseToken(ctx)
 4        if err != nil {
 5            return nil, status.Error(codes.Unauthenticated, "Invalid token")
 6        }
 7        
 8        if !token.Valid {
 9            return nil, status.Error(codes.Unauthenticated, "Token is not valid")
10        }
11        
12        claims := token.Claims.(jwt.MapClaims)
13        newCtx := context.WithValue(ctx, "user_id", claims["user_id"])
14        newCtx = context.WithValue(newCtx, "username", claims["username"])
15        
16        return handler(newCtx, req)
17    }
18}

Token Refresh

 1func RefreshTokenHandler(jwtHandler *authn.JWTHandler) gin.HandlerFunc {
 2    return func(c *gin.Context) {
 3        newToken, err := jwtHandler.RefreshToken(c)
 4        if err != nil {
 5            if err == authn.ErrExpiredToken {
 6                c.JSON(401, gin.H{"error": "Token has expired and cannot be refreshed"})
 7                return
 8            }
 9            c.JSON(500, gin.H{"error": "Failed to refresh token"})
10            return
11        }
12        
13        c.JSON(200, gin.H{"token": newToken})
14    }
15}

RSA Key Configuration

 1func SetupRSAJWT() (*authn.JWTHandler, error) {
 2    cfg := &authn.Config{
 3        Realm:            "MyApp",
 4        SigningAlgorithm: "RS256",
 5        PriKeyFile:       "/path/to/private.pem",
 6        PubKeyFile:       "/path/to/public.pem",
 7        Timeout:          time.Hour * 24,
 8        TokenLookup:      "header:Authorization",
 9        TokenHeadName:    "Bearer",
10    }
11    
12    return authn.New(cfg)
13}
14
15// Or using key bytes directly
16func SetupRSAJWTWithBytes(privateKeyBytes, publicKeyBytes []byte) (*authn.JWTHandler, error) {
17    cfg := &authn.Config{
18        Realm:            "MyApp",
19        SigningAlgorithm: "RS256",
20        PriKeyBytes:      privateKeyBytes,
21        PubKeyBytes:      publicKeyBytes,
22        Timeout:          time.Hour * 24,
23    }
24    
25    return authn.New(cfg)
26}

Flexible Token Extraction

 1// Extract from different sources
 2func DemoTokenExtraction() {
 3    // From header (default)
 4    cfg1 := &authn.Config{
 5        TokenLookup: "header:Authorization",
 6        // ... other config
 7    }
 8    
 9    // From query parameter
10    cfg2 := &authn.Config{
11        TokenLookup: "query:token",
12        // ... other config
13    }
14    
15    // From cookie
16    cfg3 := &authn.Config{
17        TokenLookup: "cookie:jwt_token",
18        // ... other config
19    }
20    
21    // From form data
22    cfg4 := &authn.Config{
23        TokenLookup: "form:access_token",
24        // ... other config
25    }
26    
27    // From URL parameter
28    cfg5 := &authn.Config{
29        TokenLookup: "param:token",
30        // ... other config
31    }
32}

Error Handling

 1func HandleJWTErrors(err error) {
 2    switch err {
 3    case authn.ErrExpiredToken:
 4        // Token has expired
 5        log.Println("Token expired")
 6    case authn.ErrMissingSecretKey:
 7        // Secret key not provided
 8        log.Println("Secret key missing")
 9    case authn.ErrEmptyAuthHeader:
10        // Authorization header is empty
11        log.Println("Auth header empty")
12    case authn.ErrInvalidAuthHeader:
13        // Authorization header format is invalid
14        log.Println("Auth header invalid")
15    case authn.ErrInvalidSigningAlgorithm:
16        // Unsupported signing algorithm
17        log.Println("Invalid signing algorithm")
18    default:
19        log.Printf("JWT error: %v", err)
20    }
21}