JWT Auth (Golang)
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
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