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: "[email protected]",
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)