golangJwtAuth

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: May 31, 2025 License: MIT Imports: 17 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.
version Node.js can get here

version

Feature

  • Dual Token System
    • Access Token (short-term) + Refresh Token (long-term)
    • Automatic token refresh without requiring re-login
    • ES256 algorithm (Elliptic Curve Digital Signature)
  • Device Fingerprinting
    • Generates unique fingerprints based on User-Agent, Device ID, OS, and Browser
    • Prevents token misuse across different devices
    • Automatic device type detection (Desktop, Mobile, Tablet)
  • Token Revocation
    • Adds Access Token to blacklist upon logout
    • Redis TTL automatically cleans expired revocation records
    • Prevents reuse of logged-out tokens
  • Version Control Protection
    • Refresh Token version tracking
    • Auto-generates new Refresh ID after 5 refresh attempts
    • Prevents replay attacks
  • Smart Refresh Strategy
    • Auto-regenerates when Refresh Token has less than half lifetime remaining
    • 5-second grace period for old tokens to reduce concurrency issues
    • Minimizes database queries
  • Multiple Authentication Methods
    • Automatic cookie reading
    • Authorization Bearer Header
    • Custom Headers (X-Device-ID, X-Refresh-ID)
  • Flexible Configuration
    • Supports file paths or direct key content
    • Customizable Cookie names
    • Production/Development environment auto-switching

How to use

  • Installation
    go get github.com/pardnchiu/golang-jwt-auth
    
  • Initialize
    package main
    
    import (
      "log"
      "time"
    
      "github.com/pardnchiu/golang-jwt-auth"
    )
    
    func main() {
      config := &golangJwtAuth.Config{
        PrivateKeyPath:       "./keys/private.pem",
        PublicKeyPath:        "./keys/public.pem", 
        // Or provide keys directly:
        // PrivateKey:           "-----BEGIN EC PRIVATE KEY-----...",
        // PublicKey:            "-----BEGIN PUBLIC KEY-----...",
        AccessTokenExpires:   15 * time.Minute,
        RefreshIdExpires:     7 * 24 * time.Hour,
        // true: domain=Domain, samesite=none, secure=true
        // false: domain=localhost, samesite=lax, secure=false
        IsProd:               false,
        Domain:               "pardn.io",
        // Cookie key names, defaults to access_token/refresh_id
        AccessTokenCookieKey: "access_token",
        RefreshIdCookieKey:   "refresh_id",
        // Redis storage configuration
        Redis: golangJwtAuth.RedisConfig{
          Host:     "localhost",
          Port:     6379,
          Password: "",
          DB:       0,
        },
        CheckUserExists: func(user golangJwtAuth.AuthData) (bool, error) {
          // Return true if user exists, false otherwise
          return true, nil
        },
        // Maximum version threshold, default 5
        MaxVersion: 5,
        // TTL threshold, default 0.5
        RefreshTTL: 0.5
      }
    
      jwtAuth, err := golangJwtAuth.New(config)
      if err != nil {
        log.Fatal("failed to init:", err)
      }
      defer jwtAuth.Close()
    }
    
Create()
func loginHandler(jwtAuth *golangJwtAuth.JWTAuth) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    // after verifying user login info...
    
    userData := &golangJwtAuth.AuthData{
      ID:        "user123",
      Name:      "John Doe",
      Email:     "john@example.com",
      Thumbnail: "avatar.jpg",
      Role:      "user",
      Level:     1,
      Scope:     []string{"read", "write"},
    }

    tokenResult, err := jwtAuth.Create(r, w, userData)
    if err != nil {
      http.Error(w, err.Error(), http.StatusInternalServerError)
      return
    }

    // automatically set in cookies
    json.NewEncoder(w).Encode(map[string]interface{}{
      "success":    true,
      "token":      tokenResult.Token,
      "refresh_id": tokenResult.RefreshId,
    })
  }
}
Verify()
func protectedHandler(jwtAuth *golangJwtAuth.JWTAuth) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    result := jwtAuth.Verify(r, w)
    
    if !result.Success {
      w.WriteHeader(result.StatusCode)
      json.NewEncoder(w).Encode(map[string]string{
        "error": result.Error,
      })
      return
    }

    // Use the authenticated user data
    user := result.Data
    json.NewEncoder(w).Encode(map[string]interface{}{
      "message": "Protected resource accessed",
      "user":    user,
    })
  }
}
Revoke()
func logoutHandler(jwtAuth *golangJwtAuth.JWTAuth) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    err := jwtAuth.Revoke(r, w)
    if err != nil {
      http.Error(w, err.Error(), http.StatusInternalServerError)
      return
    }

    json.NewEncoder(w).Encode(map[string]string{
      "message": "Successfully logged out",
    })
  }
}
GinMiddleware
package main

import (
  "github.com/gin-gonic/gin"
  "github.com/pardnchiu/golang-jwt-auth"
)

func main() {
  // Initialize jwtAuth...
  
  r := gin.Default()
  
  // Apply as a global middleware
  r.Use(jwtAuth.GinMiddleware())
  
  // Or apply to specific route groups
  protected := r.Group("/api/protected")
  protected.Use(jwtAuth.GinMiddleware())
  {
    protected.GET("/profile", func(c *gin.Context) {
      // Get user data from Context
      user, exists := golangJwtAuth.GetAuthDataFromGinContext(c)
      if !exists {
        c.JSON(500, gin.H{"error": "Failed to get user data"})
        return
      }
      
      c.JSON(200, gin.H{
        "user": user,
      })
    })
  }
  
  r.Run(":8080")
}
HTTPMiddleware
package main

import (
  "net/http"
  "github.com/pardnchiu/golang-jwt-auth"
)

func main() {
  // Initialize jwtAuth...
  
  mux := http.NewServeMux()
  
  // Protected route
  mux.HandleFunc("/api/profile", func(w http.ResponseWriter, r *http.Request) {
    // Get user data from Request Context
    user, exists := golangJwtAuth.GetAuthDataFromHTTPRequest(r)
    if !exists {
      http.Error(w, "Failed to get user data", http.StatusInternalServerError)
      return
    }
    
    json.NewEncoder(w).Encode(map[string]interface{}{
      "user": user,
    })
  })
  
  // Apply middleware
  server := &http.Server{
    Addr:    ":8080",
    Handler: jwtAuth.HTTPMiddleware(mux),
  }
  
  server.ListenAndServe()
}

Configuration

Config
  • PrivateKeyPath / PrivateKey: private key file path or content
  • PublicKeyPath / PublicKey: public key file path or content
  • AccessTokenExpires: access token expire time
  • RefreshIdExpires: refresh id expire time
  • IsProd: is production or not (affects cookie setting)
  • Domain: cookie domain
  • Redis: redis connection
    • Host: redis host
    • Port: redis port
    • Password: redis password (optional)
    • Db: redis db (optional)
  • CheckUserExists: user existence check function
  • AccessTokenCookieKey: access token cookie name (default: 'access_token')
  • RefreshTokenCookieKey: refresh id cookie name (default: 'refresh_id')
Supported methods
  1. Cookie: Automatically reads token from cookie
  2. Authorization Header: Authorization: Bearer <token>
  3. Custom Headers:
    • X-Session-FP: Custom fingerprint
    • X-Refresh-ID: Custom Refresh ID
    • X-Device-ID: Device ID

Token refresh

The system automatically generates a new Refresh ID in the following cases:

  • Refresh version exceeds 5 times
  • Remaining Refresh Token time is less than half

The new tokens are returned via:

  • HTTP Header: X-New-Access-Token
  • HTTP Header: X-New-Refresh-ID
  • Cookie auto-update

Security features

  • Fingerprint recognition: Generates a unique fingerprint based on User-Agent, Device-ID, OS, Browser, and Device type
  • Token revocation: Adds token to a blacklist on logout
  • Automatic expiration: Supports TTL to automatically clean up expired tokens
  • Version control: Tracks Refresh Token versions to prevent replay attacks
  • Fingerprint validation: Ensures tokens are used from the same device/browser

Error handling

All main methods return an AuthResult struct, including:

  • Success: Whether the operation succeeded
  • StatusCode: HTTP status code
  • Error: Error message
  • Data: User data (on success)

Common error types:

  • unauthorized: Unauthorized
  • token revoked: Token has been revoked
  • fingerprint invalid: Fingerprint mismatch
  • refresh id invalid: Invalid Refresh ID
  • access token invalid: Invalid Access Token

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthData

type AuthData 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) (*AuthData, bool)

func GetAuthDataFromHTTPRequest

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

type AuthResult

type AuthResult struct {
	Success    bool      `json:"success"`
	Data       *AuthData `json:"data,omitempty"`
	Error      string    `json:"error,omitempty"`
	StatusCode int       `json:"status_code"`
}

type Config

type Config struct {
	PrivateKeyPath       string                       `json:"private_key_path"`               // Path to private key file
	PublicKeyPath        string                       `json:"public_key_path"`                // Path to public key file
	PrivateKey           string                       `json:"private_key,omitempty"`          // Or directly provide private key content
	PublicKey            string                       `json:"public_key,omitempty"`           // Or directly provide public key content
	AccessTokenExpires   time.Duration                `json:"access_token_expires,omitempty"` // Default 15 minutes
	RefreshIdExpires     time.Duration                `json:"refresh_id_expires,omitempty"`   // Default 7 days
	IsProd               bool                         `json:"is_prod"`                        // Default false
	Domain               string                       `json:"domain,omitempty"`               // Default localhost
	Redis                RedisConfig                  `json:"redis"`
	CheckUserExists      func(AuthData) (bool, error) `json:"-"`
	AccessTokenCookieKey string                       `json:"access_token_cookie_key,omitempty"` // Default access_token
	RefreshIdCookieKey   string                       `json:"refresh_id_cookie_key,omitempty"`   // Default refresh_id
	MaxVersion           int                          `json:"max_version,omitempty"`             // Version threshold, default 5
	RefreshTTL           float64                      `json:"refresh_ttl,omitempty"`             // TTL threshold, default 0.5
	LogPath              string                       `json:"log_path,omitempty"`
	PrivateKeyPEM        *ecdsa.PrivateKey            `json:"-"`
	PublicKeyPEM         *ecdsa.PublicKey             `json:"-"`
}

type JWTAuth

type JWTAuth struct {
	Config  *Config
	Redis   *redis.Client
	Context context.Context
	Logger  *Logger
}

func New

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

func (*JWTAuth) Close

func (j *JWTAuth) Close() error

func (*JWTAuth) Create

func (j *JWTAuth) Create(r *http.Request, w http.ResponseWriter, u *AuthData) (*TokenResult, error)

func (*JWTAuth) GinMiddleware

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

func (*JWTAuth) HTTPMiddleware

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

func (*JWTAuth) Refresh

func (j *JWTAuth) Refresh(r *http.Request, w http.ResponseWriter, refreshId string, fp string) *AuthResult

func (*JWTAuth) Revoke

func (j *JWTAuth) Revoke(r *http.Request, w http.ResponseWriter) error

func (*JWTAuth) Verify

func (j *JWTAuth) Verify(r *http.Request, w http.ResponseWriter) *AuthResult

type Logger added in v0.4.0

type Logger struct {
	InitLogger    *log.Logger
	CreateLogger  *log.Logger
	RefreshLogger *log.Logger
	VerifyLogger  *log.Logger
	RevokeLogger  *log.Logger
	Path          string
}

func NewLogger added in v0.4.0

func NewLogger(path string) (*Logger, error)

func (*Logger) Create added in v0.4.0

func (l *Logger) Create(isError bool, message ...string)

func (*Logger) Init added in v0.4.0

func (l *Logger) Init(isError bool, message ...string)

func (*Logger) Refresh added in v0.4.0

func (l *Logger) Refresh(isError bool, message ...string)

func (*Logger) Verify added in v0.4.0

func (l *Logger) Verify(isError bool, message ...string)

type RedisConfig

type RedisConfig struct {
	Host     string `json:"host"`
	Port     int    `json:"port"`
	Password string `json:"password,omitempty"`
	DB       int    `json:"db"`
}

type RefreshData

type RefreshData struct {
	Data        *AuthData `json:"data,omitempty"`
	Version     int       `json:"version"`
	Fingerprint string    `json:"fp"`
	EXP         int64     `json:"exp"`
	IAT         int64     `json:"iat"`
	JTI         string    `json:"jti"`
}

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