golang-jwt
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)))
}