Note
This content is translated by LLM. Original text can be found here
A Golang JWT authentication library providing access and refresh tokens with fingerprint recognition, Redis storage, and automatic refresh functionality.
Access Token paired with Refresh ID, featuring an automatic refresh mechanism.
Generates a unique fingerprint based on User-Agent
, Device ID
, operating system, and browser to prevent token misuse across devices.
Token revocation, version control, intelligent refresh, and concurrency protection using Redis locking.
Click to view
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{Check if user exists}
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]
github.com/gin-gonic/gin
github.com/golang-jwt/jwt/v5
github.com/redis/go-redis/v9
(< v0.3.1)github.com/pardnchiu/go-logger
Starting fromv0.3.1
, non-standard libraries are deprecated for performance and stability. Replaced withlog/slog
.
go get github.com/pardnchiu/go-jwt
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
goJwt "github.com/pardnchiu/go-jwt"
)
func main() {
config := goJwt.Config{
Redis: goJwt.Redis{
Host: "localhost",
Port: 6379,
Password: "password",
DB: 0,
},
CheckAuth: func(userData goJwt.Auth) (bool, error) {
// Custom user validation logic
return userData.ID != "", nil
},
}
auth, err := goJwt.New(config)
if err != nil {
log.Fatal("Initialization failed:", err)
}
defer auth.Close()
r := gin.Default()
// Login endpoint
r.POST("/login", func(c *gin.Context) {
// After validating login credentials...
user := &goJwt.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, _ := goJwt.GetAuthDataFromGinContext(c)
c.JSON(http.StatusOK, gin.H{"user": user})
})
}
// Logout
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": "Logged out successfully"})
})
r.Run(":8080")
}
type Config struct {
Redis Redis // Redis configuration (required)
File *File // File configuration for key management (optional)
Option *Option // System parameters and token settings (optional)
Cookie *Cookie // Cookie security settings (optional)
CheckAuth func(Auth) (bool, error) // User authentication 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 string means no auth)
DB int // Redis database index (required, usually 0-15)
}
type File struct {
PrivateKeyPath string // ECDSA private key file path for JWT signing
PublicKeyPath string // ECDSA public key file path for JWT verification
}
type Option struct {
PrivateKey string // ECDSA private key content (auto-generates P-256 if not provided)
PublicKey string // ECDSA public key content (auto-generates P-256 if not provided)
AccessTokenExpires time.Duration // Access token expiration time (default: 15 minutes)
RefreshIdExpires time.Duration // Refresh ID expiration time (default: 7 days)
AccessTokenCookieKey string // Access token cookie name (default: "access_token")
RefreshIdCookieKey string // Refresh ID cookie name (default: "refresh_id")
MaxVersion int // Maximum version count for refresh tokens (default: 5)
RefreshTTL float64 // Refresh threshold as proportion of TTL (default: 0.5)
}
type Cookie struct {
Domain *string // Cookie domain scope (nil means current domain)
Path *string // Cookie path scope (default: "/")
SameSite *http.SameSite // Cookie SameSite policy (default: Lax for CSRF prevention)
Secure *bool // Cookie secure flag, HTTPS only (default: false)
HttpOnly *bool // Cookie HttpOnly flag for XSS prevention (default: true)
}
-
New()
- Create a new instanceauth, err := goJwt.New(config)
- Initializes Redis connection
- Auto-generates ECDSA keys if not provided
- Validates configuration
-
Close()
- Close the instanceerr := auth.Close()
- Closes Redis connection
- Releases system resources
-
Create()
- Generate a new JWTresult := auth.Create(w, r, userData)
- Generates access token and refresh ID
- Sets secure cookies
- Stores session data in Redis
-
Verify()
- Verify JWTresult := auth.Verify(w, r)
- Parses and validates JWT token
- Checks device fingerprint
- Automatically refreshes if needed
- Returns user data
-
Revoke()
- Terminate JWTresult := auth.Revoke(w, r)
- Clears cookies
- Adds token to blacklist
- Updates Redis records
// Gin framework middleware
protected.Use(auth.GinMiddleware())
// Standard HTTP middleware
server := &http.Server{
Handler: auth.HTTPMiddleware(handler),
}
// Get user data from context
user, exists := goJwt.GetAuthDataFromGinContext(c)
user, exists := goJwt.GetAuthDataFromHTTPRequest(r)
r.Header.Set("X-Device-FP", fingerprint)
r.Header.Set("X-Refresh-ID", refreshID)
r.Header.Set("Authorization", "Bearer "+token)
access_token, refresh_id cookies
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 category tag
}
data_missing
- Missing required datadata_invalid
- Invalid data formatunauthorized
- Authentication failedrevoked
- Token revokedfailed_to_update
- Update operation failedfailed_to_create
- Creation operation failedfailed_to_sign
- Token signing failedfailed_to_store
- Storage operation failedfailed_to_get
- Retrieval operation failed
This project is licensed under the MIT license.
©️ 2025 邱敬幃 Pardn Chiu