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}