golang-jwt

authentication-libraryGoJWTJSON-Web-Tokensecuritytoken-authenticationdigital-signature

Authentication Library

golang-jwt

Overview

golang-jwt is a pure JWT (JSON Web Token) implementation library for Go that provides complete JWT encoding, decoding, and validation functionality in full compliance with RFC 7519 standards. It supports various signing algorithms including HMAC, RSA, ECDSA, and EdDSA, with flexible handling of custom claims and advanced security features. While offering simple and intuitive APIs, it has excellent design for both performance and security, and is used in a wide range of use cases including microservices, API authentication, and single sign-on. As of 2025, it continues to be actively developed and is one of the most reliable libraries in the Go JWT ecosystem.

Details

golang-jwt is a Go library that provides complete lifecycle management for JWT tokens. It offers the following key features:

  • RFC 7519 Compliance: Safe and reliable implementation with full compliance to JWT standard specifications
  • Diverse Signing Algorithms: Support for HMAC (HS256/384/512), RSA (RS256/384/512), ECDSA (ES256/384/512), and EdDSA
  • Custom Claims: Flexible claims processing and validation functionality
  • Security Features: Token expiration, Issuer/Audience validation, and custom validation
  • Performance: High-speed parsing and memory-efficient design
  • Extensibility: Support for custom key functions, token parsers, and validator implementations

Pros and Cons

Pros

  • High interoperability with other systems through complete compliance with JWT standards
  • Support for wide range of security requirements through rich signing algorithm support
  • Provides advanced customizability while maintaining simple APIs
  • Excellent processing performance and memory efficiency
  • Lightweight with few dependencies, designed for microservices
  • Active development community with rich documentation and samples

Cons

  • High-level authentication features not included as pure JWT library
  • Requires developers to understand JWT security and implement appropriately
  • Cannot be used in non-Go language environments
  • Token persistence and session management functionality requires separate implementation

Reference Pages

Code Examples

Basic JWT Token Generation and Validation

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

// Custom claims structure
type CustomClaims struct {
	UserID   string `json:"user_id"`
	Username string `json:"username"`
	Role     string `json:"role"`
	jwt.RegisteredClaims
}

var secretKey = []byte("your-256-bit-secret")

func main() {
	// JWT token generation
	token, err := generateJWT("user-123", "johndoe", "admin")
	if err != nil {
		log.Fatal("Failed to generate token:", err)
	}
	fmt.Println("Generated JWT token:", token)

	// JWT token validation
	claims, err := validateJWT(token)
	if err != nil {
		log.Fatal("Failed to validate token:", err)
	}
	fmt.Printf("Validation successful - User ID: %s, Username: %s, Role: %s\n",
		claims.UserID, claims.Username, claims.Role)
}

// JWT token generation function
func generateJWT(userID, username, role string) (string, error) {
	// Create custom claims
	claims := CustomClaims{
		UserID:   userID,
		Username: username,
		Role:     role,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			NotBefore: jwt.NewNumericDate(time.Now()),
			Issuer:    "your-app",
			Subject:   userID,
			Audience:  []string{"your-api"},
			ID:        fmt.Sprintf("jwt-%d", time.Now().Unix()),
		},
	}

	// Create token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// Sign and return as string
	tokenString, err := token.SignedString(secretKey)
	if err != nil {
		return "", fmt.Errorf("failed to sign token: %w", err)
	}

	return tokenString, nil
}

// JWT token validation function
func validateJWT(tokenString string) (*CustomClaims, error) {
	// Parse and validate token
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		// Validate signing method
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return secretKey, nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to parse token: %w", err)
	}

	// Get and validate claims
	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, fmt.Errorf("invalid token")
}

JWT Processing with RSA Public Key Cryptography

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"log"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

var (
	privateKey *rsa.PrivateKey
	publicKey  *rsa.PublicKey
)

func init() {
	// Generate RSA key pair (load from file in practice)
	var err error
	privateKey, err = rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatal("Private key generation error:", err)
	}
	publicKey = &privateKey.PublicKey
}

func main() {
	// Generate JWT token signed with RSA
	token, err := generateRSAJWT("user-456", "alice", "user")
	if err != nil {
		log.Fatal("RSA JWT generation error:", err)
	}
	fmt.Println("RSA JWT token:", token)

	// Validate with RSA public key
	claims, err := validateRSAJWT(token)
	if err != nil {
		log.Fatal("RSA JWT validation error:", err)
	}
	fmt.Printf("RSA JWT validation successful - User: %s, Role: %s\n",
		claims.Username, claims.Role)
}

// Generate JWT with RSA private key
func generateRSAJWT(userID, username, role string) (string, error) {
	claims := CustomClaims{
		UserID:   userID,
		Username: username,
		Role:     role,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			NotBefore: jwt.NewNumericDate(time.Now()),
			Issuer:    "rsa-issuer",
			Subject:   userID,
			Audience:  []string{"rsa-api"},
		},
	}

	// Sign with RSA256
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
	tokenString, err := token.SignedString(privateKey)
	if err != nil {
		return "", fmt.Errorf("failed to sign RSA token: %w", err)
	}

	return tokenString, nil
}

// Validate JWT with RSA public key
func validateRSAJWT(tokenString string) (*CustomClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		// Validate RSA signing method
		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return publicKey, nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to parse RSA token: %w", err)
	}

	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, fmt.Errorf("invalid RSA token")
}

// Example of loading keys in PEM format
func loadRSAKeysFromFile() {
	// Load private key
	privateKeyPEM := `-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----`

	block, _ := pem.Decode([]byte(privateKeyPEM))
	if block == nil || block.Type != "RSA PRIVATE KEY" {
		log.Fatal("Failed to decode PEM block containing private key")
	}

	parsedKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		log.Fatal("Private key parsing error:", err)
	}
	privateKey = parsedKey

	// Load public key
	publicKeyPEM := `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`

	block, _ = pem.Decode([]byte(publicKeyPEM))
	if block == nil || block.Type != "PUBLIC KEY" {
		log.Fatal("Failed to decode PEM block containing public key")
	}

	parsedPubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		log.Fatal("Public key parsing error:", err)
	}

	publicKey = parsedPubKey.(*rsa.PublicKey)
}

JWT Middleware Implementation

package main

import (
	"context"
	"net/http"
	"strings"

	"github.com/golang-jwt/jwt/v5"
)

// JWT middleware function
func jwtMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Extract token from Authorization header
		authHeader := r.Header.Get("Authorization")
		if authHeader == "" {
			http.Error(w, "Authorization header required", http.StatusUnauthorized)
			return
		}

		// Validate Bearer token format
		parts := strings.Split(authHeader, " ")
		if len(parts) != 2 || parts[0] != "Bearer" {
			http.Error(w, "Invalid authorization header format", http.StatusUnauthorized)
			return
		}

		tokenString := parts[1]

		// Validate JWT token
		claims, err := validateJWT(tokenString)
		if err != nil {
			http.Error(w, "Invalid token: "+err.Error(), http.StatusUnauthorized)
			return
		}

		// Set user information in context
		ctx := context.WithValue(r.Context(), "user", claims)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

// Role-based access control middleware
func requireRole(role string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			user := r.Context().Value("user").(*CustomClaims)
			if user.Role != role {
				http.Error(w, "Insufficient permissions", http.StatusForbidden)
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

// Scope-based access control middleware
func requireScope(requiredScope string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			user := r.Context().Value("user").(*CustomClaims)
			
			// Get scope information from custom claims (example)
			if scopes, ok := user.RegisteredClaims.Subject.(string); ok {
				if !strings.Contains(scopes, requiredScope) {
					http.Error(w, "Insufficient scope", http.StatusForbidden)
					return
				}
			}
			
			next.ServeHTTP(w, r)
		})
	}
}

// HTTP server setup example
func setupServer() {
	mux := http.NewServeMux()

	// Public endpoints
	mux.HandleFunc("/login", loginHandler)
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Welcome to JWT API"))
	})

	// Endpoints requiring authentication
	protected := http.NewServeMux()
	protected.HandleFunc("/profile", profileHandler)
	protected.HandleFunc("/data", dataHandler)

	// Admin-only accessible endpoints
	admin := http.NewServeMux()
	admin.HandleFunc("/admin/users", adminUsersHandler)
	admin.HandleFunc("/admin/settings", adminSettingsHandler)

	// Apply middleware
	mux.Handle("/api/", http.StripPrefix("/api", jwtMiddleware(protected)))
	mux.Handle("/admin/", requireRole("admin")(jwtMiddleware(admin)))

	log.Println("Server starting on :8080")
	log.Fatal(http.ListenAndServe(":8080", mux))
}

// Handler function examples
func loginHandler(w http.ResponseWriter, r *http.Request) {
	// Simple login processing
	username := r.FormValue("username")
	password := r.FormValue("password")

	if username == "admin" && password == "password" {
		token, err := generateJWT("admin-123", "admin", "admin")
		if err != nil {
			http.Error(w, "Token generation failed", http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(fmt.Sprintf(`{"token": "%s", "expires_in": 86400}`, token)))
	} else {
		http.Error(w, "Invalid credentials", http.StatusUnauthorized)
	}
}

func profileHandler(w http.ResponseWriter, r *http.Request) {
	user := r.Context().Value("user").(*CustomClaims)
	w.Header().Set("Content-Type", "application/json")
	w.Write([]byte(fmt.Sprintf(`{
		"user_id": "%s",
		"username": "%s",
		"role": "%s",
		"expires_at": "%s"
	}`, user.UserID, user.Username, user.Role, user.ExpiresAt.Time)))
}

func adminUsersHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.Write([]byte(`{"users": ["user1", "user2", "user3"]}`))
}

Custom Validation and Claims Processing

package main

import (
	"fmt"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

// Extended claims structure
type ExtendedClaims struct {
	UserID      string                 `json:"user_id"`
	Username    string                 `json:"username"`
	Role        string                 `json:"role"`
	Permissions []string               `json:"permissions"`
	Metadata    map[string]interface{} `json:"metadata"`
	jwt.RegisteredClaims
}

// Custom validation method
func (c ExtendedClaims) Valid() error {
	// Execute standard validation
	if err := c.RegisteredClaims.Valid(); err != nil {
		return err
	}

	// Custom validation rules
	if c.UserID == "" {
		return fmt.Errorf("user_id is required")
	}

	if c.Username == "" {
		return fmt.Errorf("username is required")
	}

	validRoles := []string{"admin", "user", "guest"}
	roleValid := false
	for _, validRole := range validRoles {
		if c.Role == validRole {
			roleValid = true
			break
		}
	}
	if !roleValid {
		return fmt.Errorf("invalid role: %s", c.Role)
	}

	// Permission validation
	if len(c.Permissions) == 0 {
		return fmt.Errorf("at least one permission is required")
	}

	return nil
}

// Extended JWT generation function
func generateExtendedJWT(userID, username, role string, permissions []string, metadata map[string]interface{}) (string, error) {
	claims := ExtendedClaims{
		UserID:      userID,
		Username:    username,
		Role:        role,
		Permissions: permissions,
		Metadata:    metadata,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(4 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			NotBefore: jwt.NewNumericDate(time.Now()),
			Issuer:    "extended-jwt-service",
			Subject:   userID,
			Audience:  []string{"api-service", "web-app"},
			ID:        fmt.Sprintf("ext-jwt-%d", time.Now().UnixNano()),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(secretKey)
}

// Extended JWT validation function
func validateExtendedJWT(tokenString string) (*ExtendedClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &ExtendedClaims{}, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return secretKey, nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to parse extended token: %w", err)
	}

	if claims, ok := token.Claims.(*ExtendedClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, fmt.Errorf("invalid extended token")
}

// Permission check helper function
func hasPermission(claims *ExtendedClaims, requiredPermission string) bool {
	for _, permission := range claims.Permissions {
		if permission == requiredPermission {
			return true
		}
	}
	return false
}

// Usage example
func extendedJWTExample() {
	// Create metadata
	metadata := map[string]interface{}{
		"department":    "engineering",
		"employee_id":   "EMP-12345",
		"last_login":    time.Now().Format(time.RFC3339),
		"login_count":   42,
		"is_verified":   true,
		"preferences": map[string]interface{}{
			"theme":    "dark",
			"language": "en",
		},
	}

	// Permission list
	permissions := []string{"read:profile", "write:profile", "read:documents", "create:reports"}

	// Generate extended JWT
	token, err := generateExtendedJWT("emp-12345", "john.doe", "user", permissions, metadata)
	if err != nil {
		log.Fatal("Extended JWT generation error:", err)
	}
	fmt.Println("Extended JWT token:", token)

	// Validate extended JWT
	claims, err := validateExtendedJWT(token)
	if err != nil {
		log.Fatal("Extended JWT validation error:", err)
	}

	fmt.Printf("Validation successful - User: %s, Department: %s\n",
		claims.Username, claims.Metadata["department"])

	// Permission check
	if hasPermission(claims, "create:reports") {
		fmt.Println("Has report creation permission")
	} else {
		fmt.Println("No report creation permission")
	}
}

JWT Refresh Token Implementation

package main

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

// Refresh token claims
type RefreshClaims struct {
	UserID    string `json:"user_id"`
	TokenType string `json:"token_type"`
	jwt.RegisteredClaims
}

// Token pair structure
type TokenPair struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int64  `json:"expires_in"`
}

// Generate access token and refresh token pair
func generateTokenPair(userID, username, role string) (*TokenPair, error) {
	accessTokenExpiry := time.Now().Add(15 * time.Minute) // Short expiration
	refreshTokenExpiry := time.Now().Add(7 * 24 * time.Hour) // Long expiration

	// Generate access token
	accessClaims := CustomClaims{
		UserID:   userID,
		Username: username,
		Role:     role,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(accessTokenExpiry),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			NotBefore: jwt.NewNumericDate(time.Now()),
			Issuer:    "token-service",
			Subject:   userID,
			Audience:  []string{"api"},
		},
	}

	accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
	accessTokenString, err := accessToken.SignedString(secretKey)
	if err != nil {
		return nil, fmt.Errorf("failed to generate access token: %w", err)
	}

	// Generate refresh token
	refreshID, err := generateRandomID()
	if err != nil {
		return nil, fmt.Errorf("failed to generate refresh token ID: %w", err)
	}

	refreshClaims := RefreshClaims{
		UserID:    userID,
		TokenType: "refresh",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(refreshTokenExpiry),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			NotBefore: jwt.NewNumericDate(time.Now()),
			Issuer:    "token-service",
			Subject:   userID,
			ID:        refreshID,
		},
	}

	refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
	refreshTokenString, err := refreshToken.SignedString(secretKey)
	if err != nil {
		return nil, fmt.Errorf("failed to generate refresh token: %w", err)
	}

	return &TokenPair{
		AccessToken:  accessTokenString,
		RefreshToken: refreshTokenString,
		TokenType:    "Bearer",
		ExpiresIn:    int64(accessTokenExpiry.Sub(time.Now()).Seconds()),
	}, nil
}

// Update access token using refresh token
func refreshAccessToken(refreshTokenString string) (*TokenPair, error) {
	// Validate refresh token
	token, err := jwt.ParseWithClaims(refreshTokenString, &RefreshClaims{}, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return secretKey, nil
	})

	if err != nil {
		return nil, fmt.Errorf("invalid refresh token: %w", err)
	}

	refreshClaims, ok := token.Claims.(*RefreshClaims)
	if !ok || !token.Valid {
		return nil, fmt.Errorf("invalid refresh token claims")
	}

	if refreshClaims.TokenType != "refresh" {
		return nil, fmt.Errorf("not a refresh token")
	}

	// Check refresh token blacklist (use database or Redis in practice)
	if isTokenBlacklisted(refreshClaims.ID) {
		return nil, fmt.Errorf("refresh token has been revoked")
	}

	// Get user information (from database in practice)
	userInfo := getUserInfo(refreshClaims.UserID)
	if userInfo == nil {
		return nil, fmt.Errorf("user not found")
	}

	// Generate new token pair
	newTokenPair, err := generateTokenPair(userInfo.UserID, userInfo.Username, userInfo.Role)
	if err != nil {
		return nil, fmt.Errorf("failed to generate new token pair: %w", err)
	}

	// Add old refresh token to blacklist
	addToBlacklist(refreshClaims.ID)

	return newTokenPair, nil
}

// Generate random ID
func generateRandomID() (string, error) {
	bytes := make([]byte, 16)
	_, err := rand.Read(bytes)
	if err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}

// User information structure (example)
type UserInfo struct {
	UserID   string
	Username string
	Role     string
	Active   bool
}

// Get user information (dummy implementation)
func getUserInfo(userID string) *UserInfo {
	users := map[string]*UserInfo{
		"user-123": {UserID: "user-123", Username: "johndoe", Role: "user", Active: true},
		"admin-456": {UserID: "admin-456", Username: "admin", Role: "admin", Active: true},
	}
	return users[userID]
}

// Blacklist management (dummy implementation)
var blacklist = make(map[string]bool)

func isTokenBlacklisted(tokenID string) bool {
	return blacklist[tokenID]
}

func addToBlacklist(tokenID string) {
	blacklist[tokenID] = true
}

// HTTP handler for token refresh
func refreshTokenHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	refreshToken := r.FormValue("refresh_token")
	if refreshToken == "" {
		http.Error(w, "Refresh token required", http.StatusBadRequest)
		return
	}

	newTokenPair, err := refreshAccessToken(refreshToken)
	if err != nil {
		http.Error(w, "Token refresh failed: "+err.Error(), http.StatusUnauthorized)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write([]byte(fmt.Sprintf(`{
		"access_token": "%s",
		"refresh_token": "%s",
		"token_type": "%s",
		"expires_in": %d
	}`, newTokenPair.AccessToken, newTokenPair.RefreshToken, newTokenPair.TokenType, newTokenPair.ExpiresIn)))
}