JWT Auth (Golang)
A JWT authentication package providing both Access Token and Refresh Token mechanisms, featuring fingerprint recognition, Redis storage, and automatic refresh functionality.
Node.js version can be found here
Three key features
- Dual Token System: Access Token + Refresh ID, with automatic refresh
- Device Fingerprinting: Generate unique fingerprints based on user agent, device ID, OS, and browser to prevent token abuse across different devices
- Security Protection: Token revocation, version control, smart refresh, and concurrency protection with Redis lock mechanism
Flow
Click to show
flowchart TD Start([Request Start]) --> Auth{Has Access Token?} Auth -->|Yes| CheckRevoke[Check if Token is Revoked] Auth -->|No| HasRefresh{Has Refresh ID?} HasRefresh -->|No| Unauthorized[Return 401 Unauthorized] HasRefresh -->|Yes| ValidateRefresh[Validate Refresh ID] CheckRevoke --> IsRevoked{Token Revoked?} IsRevoked -->|Yes| Unauthorized IsRevoked -->|No| ParseToken[Parse Access Token] ParseToken --> TokenValid{Token Valid?} TokenValid -->|Yes| ValidateClaims[Validate Claims] TokenValid -->|No| IsExpired{Token Expired?} IsExpired -->|Yes| ParseExpiredToken[Parse Expired Token] IsExpired -->|No| InvalidToken[Return 400 Invalid Token] ParseExpiredToken --> ValidateExpiredClaims[Validate Expired Token Claims] ValidateExpiredClaims --> ExpiredClaimsValid{Refresh ID and Fingerprint Match?} ExpiredClaimsValid -->|No| InvalidClaims[Return 400 Invalid Claims] ExpiredClaimsValid -->|Yes| RefreshFlow[Enter Refresh Flow] ValidateClaims --> ClaimsValid{Claims Match?} ClaimsValid -->|No| InvalidClaims ClaimsValid -->|Yes| CheckJTI[Check JTI] CheckJTI --> JTIValid{JTI Valid?} JTIValid -->|No| Unauthorized JTIValid -->|Yes| Success[Return 200 Success] ValidateRefresh --> RefreshValid{Refresh ID Valid?} RefreshValid -->|No| Unauthorized RefreshValid -->|Yes| RefreshFlow RefreshFlow --> AcquireLock[Acquire Refresh Lock] AcquireLock --> LockSuccess{Lock Acquired?} LockSuccess -->|No| TooManyRequests[Return 429 Too Many Requests] LockSuccess -->|Yes| GetRefreshData[Get Refresh Data] GetRefreshData --> CheckTTL[Check TTL] CheckTTL --> NeedNewRefresh{Need New Refresh ID?} NeedNewRefresh -->|Yes| CreateNewRefresh[Create New Refresh ID] NeedNewRefresh -->|No| UpdateVersion[Update Version Number] CreateNewRefresh --> SetOldRefreshExpire[Set Old Refresh ID to Expire in 5 Seconds] SetOldRefreshExpire --> SetNewRefreshData[Set New Refresh Data] UpdateVersion --> SetNewRefreshData SetNewRefreshData --> CheckUserExists{User Exists Check} CheckUserExists -->|No| Unauthorized CheckUserExists -->|Yes| GenerateNewToken[Generate New Access Token] GenerateNewToken --> StoreJTI[Store New JTI] StoreJTI --> SetCookies[Set Cookies] SetCookies --> ReleaseLock[Release Lock] ReleaseLock --> RefreshSuccess[Return Refresh Success]
Dependencies
github.com/gin-gonic/gin
github.com/golang-jwt/jwt/v5
github.com/redis/go-redis/v9
github.com/pardnchiu/go-logger
How to use
Installation
go get github.com/pardnchiu/go-jwt-auth
Initialization
package main import ( "log" "net/http" "github.com/gin-gonic/gin" jwtAuth "github.com/pardnchiu/go-jwt-auth" ) func main() { // Minimal configuration - keys will be auto-generated config := jwtAuth.Config{ Redis: jwtAuth.Redis{ Host: "localhost", Port: 6379, Password: "password", DB: 0, }, CheckAuth: func(userData jwtAuth.Auth) (bool, error) { // Custom user validation logic return userData.ID != "", nil }, } auth, err := jwtAuth.New(config) if err != nil { log.Fatal("Failed to initialize:", err) } defer auth.Close() r := gin.Default() // Login endpoint r.POST("/login", func(c *gin.Context) { // After validating login credentials... user := &jwtAuth.Auth{ ID: "user123", Name: "John Doe", Email: "john@example.com", Scope: []string{"read", "write"}, } result := auth.Create(c.Writer, c.Request, user) if !result.Success { c.JSON(result.StatusCode, gin.H{"error": result.Error}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "token": result.Token.Token, "user": result.Data, }) }) // Protected routes protected := r.Group("/api") protected.Use(auth.GinMiddleware()) { protected.GET("/profile", func(c *gin.Context) { user, _ := jwtAuth.GetAuthDataFromGinContext(c) c.JSON(http.StatusOK, gin.H{"user": user}) }) } // Logout endpoint r.POST("/logout", func(c *gin.Context) { result := auth.Revoke(c.Writer, c.Request) if !result.Success { c.JSON(result.StatusCode, gin.H{"error": result.Error}) return } c.JSON(http.StatusOK, gin.H{"message": "Successfully logged out"}) }) r.Run(":8080") }
Configuration Details
type Config struct { Redis Redis // Redis configuration (required) File *File // File configuration for key management (optional) Log *Log // Logging configuration (optional) Option *Option // System parameters and token settings (optional) Cookie *Cookie // Cookie security settings (optional) CheckAuth func(Auth) (bool, error) // User authentication validation function (optional) } type Redis struct { Host string // Redis server host address (required) Port int // Redis server port number (required) Password string // Redis authentication password (optional, empty for no auth) DB int // Redis database index (required, typically 0-15) } type File struct { PrivateKeyPath string // Path to ECDSA private key file for JWT signing PublicKeyPath string // Path to ECDSA public key file for JWT verification } type Log struct { Path string // Log directory path (default: ./logs/jwtAuth) Stdout bool // Enable console output logging (default: false) MaxSize int64 // Maximum log file size before rotation in bytes (default: 16MB) MaxBackup int // Number of rotated log files to retain (default: 5) Type string // Output format: "json" for slog standard, "text" for tree format (default: "text") } type Option struct { PrivateKey string // ECDSA private key content (auto-generated P-256 if not provided) PublicKey string // ECDSA public key content (auto-generated P-256 if not provided) AccessTokenExpires time.Duration // Access token expiration duration (default: 15 minutes) RefreshIdExpires time.Duration // Refresh ID expiration duration (default: 7 days) AccessTokenCookieKey string // Access token cookie name (default: "access_token") RefreshIdCookieKey string // Refresh ID cookie name (default: "refresh_id") MaxVersion int // Maximum refresh token version count (default: 5) RefreshTTL float64 // Refresh threshold as fraction of TTL (default: 0.5) } type Cookie struct { Domain *string // Cookie domain scope (nil for current domain) Path *string // Cookie path scope (default: "/") SameSite *http.SameSite // Cookie SameSite policy (default: Lax for CSRF protection) Secure *bool // Cookie secure flag for HTTPS only (default: false) HttpOnly *bool // Cookie HttpOnly flag to prevent XSS (default: true) }
Supported Operations
Core Methods
// Create new authentication session result := auth.Create(w, r, userData) // Verify authentication status result := auth.Verify(w, r) // Revoke authentication (logout) result := auth.Revoke(w, r)
Middleware Usage
// Gin framework middleware protected.Use(auth.GinMiddleware()) // Standard HTTP middleware server := &http.Server{ Handler: auth.HTTPMiddleware(handler), } // Get user data from context user, exists := jwtAuth.GetAuthDataFromGinContext(c) user, exists := jwtAuth.GetAuthDataFromHTTPRequest(r)
Authentication Methods
// Multiple authentication methods supported: // 1. Custom headers r.Header.Set("X-Device-FP", fingerprint) r.Header.Set("X-Refresh-ID", refreshID) r.Header.Set("Authorization", "Bearer "+token) // 2. Cookies (automatically managed) // access_token, refresh_id cookies // 3. Device fingerprinting (automatic) // Based on user agent, device ID, OS, browser
Core Features
Connection Management
- New - Create new JWT auth instance
auth, err := jwtAuth.New(config)
- Initialize Redis connection
- Setup logging system
- Auto-generate ECDSA keys if not provided
-
Validate configuration
- Close - Close JWT auth instance
err := auth.Close()
- Close Redis connection
- Release system resources
Security Features
- Device Fingerprinting - Generate unique fingerprints based on user agent, device ID, OS, browser, and device type
fingerprint := auth.getFingerprint(w, r)
- Token Revocation - Add tokens to blacklist on logout
result := auth.Revoke(w, r)
- Automatic Refresh - Smart token refresh based on expiration and version control
// Automatically triggered during Verify() when needed result := auth.Verify(w, r)
Authentication Flow
- Create - Generate new authentication session
result := auth.Create(w, r, userData)
- Generate access token and refresh ID
- Set secure cookies
-
Store session data in Redis
- Verify - Validate authentication status
result := auth.Verify(w, r)
- Parse and validate JWT token
- Check device fingerprint
- Auto-refresh if needed
-
Return user data
- Revoke - Terminate authentication session
result := auth.Revoke(w, r)
- Clear cookies
- Add token to blacklist
- Update Redis records
Security Features
- Device Fingerprinting: Generate unique fingerprints based on user agent, device ID, OS, browser, and device type with persistent tracking
- Token Revocation: Add tokens to blacklist on logout
- Automatic Expiration: Support TTL auto-cleanup for expired tokens
- Version Control: Track refresh token versions to prevent replay attacks
- Fingerprint Verification: Ensure tokens can only be used on the same device/browser
- Auto Key Generation: Automatically generate secure ECDSA key pairs if not provided
- Concurrency Protection: Redis lock mechanism prevents concurrent refresh conflicts
Error Handling
All methods return a JWTAuthResult
structure:
type JWTAuthResult struct { StatusCode int // HTTP status code Success bool // Whether operation succeeded Data *Auth // User data Token *TokenResult // Token information Error string // Error message ErrorTag string // Error classification tag }
Error Tags
-
data_missing
- Required data not provided -
data_invalid
- Invalid data format -
unauthorized
- Authentication failed -
revoked
- Token has been revoked -
failed_to_update
- Update operation failed -
failed_to_create
- Creation operation failed -
failed_to_sign
- Token signing failed -
failed_to_store
- Storage operation failed -
failed_to_get
- Retrieval operation failed
License
This source code project is licensed under the MIT License.
©️ 2025 邱敬幃 Pardn Chiu
Top comments (0)