Skip to content

krajcik/go-jwt

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

JWT Package - Generic Claims Architecture

A high-performance JWT (JSON Web Token) implementation for Go with support for custom claims types and verification-only mode for microservices architecture.

Go Reference Go Report Card Coverage Go Version Tests License: MIT

🎯 Architecture Overview

This JWT package is designed for microservices architectures where:

  • Authentication Service generates tokens (has private key)
  • Client Services only verify tokens (have public key or use verification endpoint)
  • Each service can have its own custom claims structure

✨ Key Features

  • πŸ”§ Custom Claims: Type-safe custom claims for each service
  • πŸ” Asymmetric Algorithms: RSA, ECDSA, EdDSA support for distributed systems
  • πŸ”‘ Verification-only Mode: Services can verify without knowing secrets
  • πŸš€ High Performance: Buffer pooling and optimized parsing
  • πŸ›‘οΈ Security First: Built-in validation and expiration checking
  • 🧡 Thread Safe: Concurrent operations supported
  • 🎯 Type Safety: Generics for compile-time claim validation

πŸ“¦ Supported Algorithms

Algorithm Family Algorithms Key Type Best For
HMAC HS256, HS384, HS512 Symmetric (shared secret) Internal microservices
RSA RS256, RS384, RS512 Asymmetric (RSA keys) Public/private separation
ECDSA ES256, ES384, ES512 Asymmetric (ECDSA keys) Efficient asymmetric
EdDSA Ed25519 Asymmetric (Ed25519 keys) Modern, fast asymmetric

πŸš€ Quick Start

1. Authentication Service (Token Generation)

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "fmt"
    "time"
    
    "github.com/krajcik/go-jwt"
)

// Define your custom claims
type AuthClaims struct {
    UserID    string   `json:"user_id"`
    Email     string   `json:"email"`
    Roles     []string `json:"roles"`
    TokenType string   `json:"token_type"`
}

// Implement validation (optional)
func (c AuthClaims) Validate() error {
    if c.UserID == "" {
        return errors.New("user_id is required")
    }
    return nil
}

func main() {
    // Generate RSA key pair for Auth Service
    privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
    
    // Create token signer
    algorithm := jwt.NewRS256(privateKey)
    signer, _ := jwt.NewTokenSigner[AuthClaims](algorithm, "auth-service")
    
    // Generate token with custom claims
    customClaims := AuthClaims{
        UserID:    "user_12345",
        Email:     "[email protected]",
        Roles:     []string{"user", "admin"},
        TokenType: "access",
    }
    
    token, err := signer.GenerateToken("user_12345", time.Hour, customClaims)
    if err != nil {
        panic(err)
    }
    
    fmt.Println("Generated token:", token)
}

2. Client Service (Token Verification)

package main

import (
    "crypto/rsa"
    "fmt"
    
    "github.com/krajcik/go-jwt"
)

// Client service defines its own claims structure
type ClientClaims struct {
    UserID      string `json:"user_id"`
    Permissions []string `json:"permissions"`
}

func main() {
    // Client service only has public key
    var publicKey *rsa.PublicKey // obtained from Auth Service
    
    // Create token verifier (verification-only)
    algorithm := jwt.NewRS256WithPublicKey(publicKey)
    verifier, _ := jwt.NewTokenVerifier[ClientClaims](algorithm, "auth-service")
    
    // Verify token from request
    token := "eyJ..." // from Authorization header
    
    standardClaims, customClaims, err := verifier.VerifyToken(token)
    if err != nil {
        panic(err)
    }
    
    // Use standard claims
    userID := standardClaims.Subject
    expiry := standardClaims.ExpiresAt
    
    // Use custom claims
    permissions := customClaims.Permissions
    
    fmt.Printf("User: %s, Permissions: %v, Expires: %d\n", 
        userID, permissions, expiry)
}

πŸ—οΈ Service-Specific Examples

E-commerce Service

type EcommerceClaims struct {
    CartID       string `json:"cart_id"`
    CustomerTier string `json:"customer_tier"`
    StoreID      string `json:"store_id"`
}

func (c EcommerceClaims) Validate() error {
    validTiers := map[string]bool{
        "bronze": true, "silver": true, "gold": true, "platinum": true,
    }
    if !validTiers[c.CustomerTier] {
        return errors.New("invalid customer tier")
    }
    return nil
}

// Usage in e-commerce service
algorithm := jwt.NewRS256WithPublicKey(publicKey)
verifier, _ := jwt.NewTokenVerifier[EcommerceClaims](algorithm, "auth-service")

standardClaims, ecommerceClaims, err := verifier.VerifyToken(token)
if err != nil {
    return http.StatusUnauthorized, err
}

// Use e-commerce specific claims
cartID := ecommerceClaims.CartID
customerTier := ecommerceClaims.CustomerTier

Payment Service

type PaymentClaims struct {
    PaymentMethodID string  `json:"payment_method_id"`
    MaxAmount       float64 `json:"max_amount"`
    Currency        string  `json:"currency"`
    MerchantID      string  `json:"merchant_id"`
}

func (c PaymentClaims) Validate() error {
    if c.MaxAmount <= 0 {
        return errors.New("max_amount must be positive")
    }
    return nil
}

// Usage in payment service
algorithm := jwt.NewRS256WithPublicKey(publicKey)
verifier, _ := jwt.NewTokenVerifier[PaymentClaims](algorithm, "auth-service")

_, paymentClaims, err := verifier.VerifyToken(token)
if err != nil {
    return errors.New("invalid payment token")
}

// Validate payment amount against token limits
if requestAmount > paymentClaims.MaxAmount {
    return errors.New("amount exceeds token limit")
}

πŸ” Security Architecture

Asymmetric Key Distribution

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    Private Key    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Auth Service   β”‚ ◄─────────────── β”‚   Key Store     β”‚
β”‚ (Token Signing) β”‚                   β”‚                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                                      β”‚
         β”‚ Tokens                               β”‚ Public Key
         β–Ό                                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Client Service  β”‚ ◄─────────────── β”‚ Client Service  β”‚
β”‚   (Verify)      β”‚    Public Key     β”‚   (Verify)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

HMAC Shared Secret (Internal Services)

// For internal microservices that can share secrets
algorithm := jwt.NewHS256("shared-internal-secret")

// Both services can sign and verify
signer, _ := jwt.NewTokenSigner[InternalClaims](algorithm, "internal")
verifier, _ := jwt.NewTokenVerifier[InternalClaims](algorithm, "internal")

πŸ“‹ Standard Claims

All tokens include standard JWT claims:

type StandardClaims struct {
    Subject   string `json:"sub"`      // User/entity identifier
    IssuedAt  int64  `json:"iat"`      // Issued at timestamp
    ExpiresAt int64  `json:"exp"`      // Expiration timestamp
    NotBefore int64  `json:"nbf"`      // Not valid before (optional)
    Issuer    string `json:"iss"`      // Token issuer
    Audience  string `json:"aud"`      // Token audience (optional)
    JwtID     string `json:"jti"`      // JWT ID (optional)
}

πŸ” Claims Validation

Implement ClaimsValidator interface for automatic validation:

type ClaimsValidator interface {
    Validate() error
}

type UserClaims struct {
    UserID string `json:"user_id"`
    Email  string `json:"email"`
}

func (c UserClaims) Validate() error {
    if c.UserID == "" {
        return errors.New("user_id cannot be empty")
    }
    
    emailRegex := regexp.MustCompile(`^[^\s@]+@[^\s@]+\.[^\s@]+$`)
    if c.Email != "" && !emailRegex.MatchString(c.Email) {
        return errors.New("invalid email format")
    }
    
    return nil
}

❌ Error Handling

standardClaims, customClaims, err := verifier.VerifyToken(token)
if err != nil {
    switch err {
    case jwt.ErrExpiredToken:
        return http.StatusUnauthorized, "Token expired"
    case jwt.ErrInvalidSignature:
        return http.StatusUnauthorized, "Invalid token"
    case jwt.ErrInvalidClaims:
        return http.StatusBadRequest, "Invalid claims"
    default:
        return http.StatusInternalServerError, "Token verification failed"
    }
}

πŸš€ Performance Features

  • Buffer Pooling: Reduces memory allocations
  • Manual Token Parsing: 5.6x faster than strings.Split
  • High-Performance Libraries: goccy/go-json and cloudwego/base64x
  • Zero-Copy Operations: Optimized string handling
  • Concurrent Safe: Thread-safe operations

πŸ§ͺ Testing

# Run all tests
go test ./... -v

# Run tests with coverage
go test ./... -cover

# Run specific test
go test ./... -run TestTokenSigner -v

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

🀝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ™ Acknowledgments

  • Optimized with high-performance libraries: goccy/go-json and cloudwego/base64x
  • Designed for modern microservices architectures
  • Built for production use in distributed systems

About

A high-performance JWT implementation for Go

Resources

License

Stars

Watchers

Forks

Languages