jwtAuth

package module
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 22, 2025 License: MIT Imports: 19 Imported by: 0

README

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

license version readme

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

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.

Author

邱敬幃 Pardn Chiu


©️ 2025 邱敬幃 Pardn Chiu

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Auth

type Auth struct {
	ID        string   `json:"id"`
	Name      string   `json:"name"`
	Email     string   `json:"email"`
	Thumbnail string   `json:"thumbnail,omitempty"`
	Scope     []string `json:"scope,omitempty"`
	Role      string   `json:"role,omitempty"`
	Level     int      `json:"level,omitempty"`
}

func GetAuthDataFromGinContext

func GetAuthDataFromGinContext(c *gin.Context) (*Auth, bool)

func GetAuthDataFromHTTPRequest

func GetAuthDataFromHTTPRequest(r *http.Request) (*Auth, bool)

type Config

type Config struct {
	Redis     Redis                    `json:"redis"`               // Redis 設定
	File      *File                    `json:"file,omitempty"`      // 檔案設定
	Log       *Log                     `json:"log,omitempty"`       // 日誌設定
	Option    *Option                  `json:"parameter,omitempty"` // 可調參數
	Cookie    *Cookie                  `json:"cookie,omitempty"`    // Cookie 設定
	CheckAuth func(Auth) (bool, error) `json:"-"`                   // 檢查使用者是否存在的函數
}
type Cookie struct {
	Domain   *string        `json:"domain,omitempty"`    // Cookie 的網域
	Path     *string        `json:"path,omitempty"`      // Cookie 的路徑,預設 /
	SameSite *http.SameSite `json:"same_site,omitempty"` // Cookie 的 SameSite 屬性,預設 lax
	Secure   *bool          `json:"secure,omitempty"`    // Cookie 是否安全,預設 false
	HttpOnly *bool          `json:"http_only,omitempty"` // Cookie 是否 HttpOnly,預設 true
}

type File

type File struct {
	PrivateKeyPath string `json:"private_key_path,omitempty"`
	PublicKeyPath  string `json:"public_key_path,omitempty"`
}

type JWTAuth

type JWTAuth struct {
	// contains filtered or unexported fields
}

func New

func New(c Config) (*JWTAuth, error)

func (*JWTAuth) Close

func (j *JWTAuth) Close() error

func (*JWTAuth) Create

func (j *JWTAuth) Create(w http.ResponseWriter, r *http.Request, auth *Auth) JWTAuthResult

func (*JWTAuth) GinMiddleware

func (j *JWTAuth) GinMiddleware() gin.HandlerFunc

func (*JWTAuth) HTTPMiddleware

func (j *JWTAuth) HTTPMiddleware(next http.Handler) http.Handler

func (*JWTAuth) Revoke

func (*JWTAuth) Verify

type JWTAuthResult

type JWTAuthResult struct {
	StatusCode int          `json:"status_code"`
	Success    bool         `json:"success"`
	Data       *Auth        `json:"data,omitempty"`
	Token      *TokenResult `json:"token,omitempty"`
	Error      string       `json:"error,omitempty"`
	ErrorTag   string       `json:"error_tag,omitempty"`
}

type Log

type Log = goLogger.Log

* 繼承至 pardnchiu/go-logger

type Logger

type Logger = goLogger.Logger

type Option

type Option struct {
	PrivateKey           string        `json:"private_key,omitempty"`             // 私鑰內容
	PublicKey            string        `json:"public_key,omitempty"`              // 公鑰內容
	AccessTokenExpires   time.Duration `json:"access_token_expires,omitempty"`    // Access Token 有效期限,預設 15 分鐘
	RefreshIdExpires     time.Duration `json:"refresh_id_expires,omitempty"`      // Refresh ID 有效期限,預設 7 天
	AccessTokenCookieKey string        `json:"access_token_cookie_key,omitempty"` // Access Token Cookie 鍵名,預設 access_token
	RefreshIdCookieKey   string        `json:"refresh_id_cookie_key,omitempty"`   // Refresh ID Cookie 鍵名,預設 refresh_id
	MaxVersion           int           `json:"max_version,omitempty"`             // 重刷 Refresh ID 次數,預設 5(更換 5 次 Access Token 後,Refresh ID 會被重刷)
	RefreshTTL           float64       `json:"refresh_ttl,omitempty"`             // 刷新 Refresh ID 的 TTL 閾值,預設 0.5(低於一半時間)
}

type Pem

type Pem struct {
	// contains filtered or unexported fields
}

type Redis

type Redis struct {
	Host     string `json:"host"`               // Redis 主機位址
	Port     int    `json:"port"`               // Redis 連接埠
	Password string `json:"password,omitempty"` // Redis 密碼
	DB       int    `json:"db"`                 // Redis 資料庫編號
}

type RefreshData

type RefreshData struct {
	Data        *Auth  `json:"data,omitempty"`
	Version     int    `json:"version"`
	Fingerprint string `json:"fp"`
	Exp         int64  `json:"exp"`
	Iat         int64  `json:"iat"`
	Jti         string `json:"jti"`
}

type RefreshId

type RefreshId struct {
	ID          string `json:"id"`    // 使用者 ID
	Name        string `json:"name"`  // 使用者名稱
	Email       string `json:"email"` // 電子郵件
	Fingerprint string `json:"fp"`    // 設備指紋
	Iat         int64  `json:"iat"`   // 發行時間
	Jti         string `json:"jti"`   // JWT ID
}

Refresh Data ID 結構

type TokenResult

type TokenResult struct {
	Token     string `json:"token"`
	RefreshId string `json:"refresh_id"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL