Skip to content
/ jwt Public

Secure, production-ready JWT library for Go with safe revocation, clock skew tolerance, configurable lifetimes, and comprehensive testing

License

Notifications You must be signed in to change notification settings

simp-lee/jwt

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JWT Library

Secure, configurable, test‑friendly JWT utilities for Go: safe revocation, clock skew tolerance, strong key policy, flexible refresh, and minimal API surface.

Installation

go get github.com/simp-lee/jwt

Quick Start

Basic Usage

package main

import (
    "fmt"
    "time"
    "github.com/simp-lee/jwt"
)

func main() {
    // Create JWT service with secret key (minimum 32 chars)
    service, err := jwt.New("your-super-secure-secret-key-that-is-long-enough!")
    if err != nil {
        panic(err)
    }
    defer service.Close()

    // Generate token
    token, err := service.GenerateToken("user123", []string{"admin", "user"}, time.Hour)
    if err != nil {
        panic(err)
    }

    // Validate token
    parsedToken, err := service.ValidateToken(token)
    if err != nil {
        panic(err)
    }

    fmt.Printf("User: %s, Roles: %v\n", parsedToken.UserID, parsedToken.Roles)
}

Advanced Configuration

service, err := jwt.New("your-super-secure-secret-key-that-is-long-enough!",
    jwt.WithCleanupInterval(30*time.Minute),          // Clean expired revocations every 30 min (default: 1h)
    jwt.WithUserRevocationTTL(7*24*time.Hour),        // Remember user revocations for 7 days (default: 30d, must be >= MaxTokenLifetime)
    jwt.WithMaxTokenLifetime(2*time.Hour),            // Limit all tokens to max 2 hours (default: 24h)
    jwt.WithLeeway(2*time.Minute),                    // Allow 2min clock skew between servers (default: 2m, applies to exp/iat/nbf)
    jwt.WithIssuer("my-awesome-app"),                 // Mark tokens as issued by "my-awesome-app"
    jwt.WithAudience("api-users"),                    // Mark tokens as intended for "api-users"
)
if err != nil {
    panic(err)
}
defer service.Close()

Inspect Parsed Token

// Generate token with issuer/audience
tokenString, err := service.GenerateToken("user123", []string{"admin"}, time.Hour)
if err != nil {
    panic(err)
}

// Parse token to see all fields
token, err := service.ParseToken(tokenString)
if err != nil {
    panic(err)
}

fmt.Println("UserID:", token.UserID)
fmt.Println("Roles:", token.Roles)
fmt.Println("ExpiresAt:", token.ExpiresAt)
fmt.Println("IssuedAt:", token.IssuedAt)
fmt.Println("NotBefore:", token.NotBefore)
fmt.Println("TokenID:", token.TokenID)
fmt.Println("Issuer:", token.Issuer)
fmt.Println("Audience:", token.Audience)
fmt.Println("Subject:", token.Subject)  // Maps to UserID
fmt.Println("Raw:", token.Raw)

Refresh Strategies

// Strategy 1: Refresh with original duration (preserves original token's lifetime)
newToken, err := service.RefreshToken(oldToken)

// Strategy 2: Refresh with specified duration (new duration from current time)
newToken, err := service.RefreshTokenExtend(oldToken, 2*time.Hour)

// Both strategies automatically revoke the old token and create a new one

Notes:

  • expiresIn and extendsIn must be > 0, otherwise you'll get ErrTokenCreation.
  • extendsIn must not exceed MaxTokenLifetime or you'll get ErrTokenCreation.
  • Library does not distinguish Access vs Refresh tokens; model that via roles, wrapper metadata, or separate services.
  • Enforce rotation / absolute session caps to avoid endless extension when using the extend strategy.

Convenience

// Validate and parse in one step
// Deprecated: Use ValidateToken instead.
token, err := service.ValidateAndParse(tokenString)

// Check if specific token is revoked (token-level only)
// Does NOT check user-level revocations from RevokeAllUserTokens.
// Use ValidateToken for full revocation checks.
isRevoked := service.IsTokenRevoked(tokenID)

API Reference

Service Creation

New(secretKey string, opts ...Option) (Service, error)

Creates a new JWT service with the specified secret key and options.

Security Requirements:

  • secretKey must be at least 32 characters long
  • Use a cryptographically secure random generator for production keys
  • Store the secret key securely (environment variables, secret management systems)

Configuration Options

// Set cleanup interval for expired revoked tokens
WithCleanupInterval(interval time.Duration)

// Set TTL for user revocation records
// How long to remember that a user's tokens were revoked
// Must be >= MaxTokenLifetime for security (prevents revoked tokens from becoming valid again)
// Example: If set to 7 days, any token issued before revocation remains invalid for 7 days
WithUserRevocationTTL(ttl time.Duration)

// Set maximum allowed token lifetime
// Prevents creation of excessively long-lived tokens for security compliance
// Example: If set to 2 hours, no token can be generated with expiration > 2 hours
WithMaxTokenLifetime(lifetime time.Duration)

// Set clock skew tolerance for token validation
// Allows for small time differences between servers in distributed systems
// Example: If set to 2 minutes, tokens are still valid 2 minutes after expiration
WithLeeway(leeway time.Duration)

// Set JWT issuer claim (iss) - identifies who issued the token
// Validated during token parsing to ensure tokens come from expected source
// Example: "my-app-v1" - helps distinguish tokens from different applications
WithIssuer(issuer string)

// Set JWT audience claim (aud) - identifies who the token is intended for  
// Validated during token parsing to ensure tokens are used by intended recipients
// Example: "api-users" - helps prevent token misuse across different services
WithAudience(audience string)

// Set custom clock for testing
WithClock(clock Clock)

Token Structure

type Token struct {
    UserID    string    `json:"user_id"`
    Roles     []string  `json:"roles"`
    ExpiresAt time.Time `json:"expires_at"`
    IssuedAt  time.Time `json:"issued_at"`
    NotBefore time.Time `json:"not_before,omitempty"`
    TokenID   string    `json:"token_id"`
    Issuer    string    `json:"issuer,omitempty"`
    Audience  string    `json:"audience,omitempty"`
    Subject   string    `json:"subject,omitempty"`   // Maps to UserID for compatibility
    Raw       string    `json:"raw,omitempty"`       // Original token string
}

Service Interface

type Service interface {
    GenerateToken(userID string, roles []string, expiresIn time.Duration) (string, error)
    ValidateToken(tokenString string) (*Token, error)
    ValidateAndParse(tokenString string) (*Token, error)          // Deprecated: Use ValidateToken instead
    RefreshToken(tokenString string) (string, error)             // Preserves original duration
    RefreshTokenExtend(tokenString string, extendsIn time.Duration) (string, error) // Extends with new duration
    RevokeToken(tokenString string) error
    IsTokenRevoked(tokenID string) bool
    ParseToken(tokenString string) (*Token, error)
    RevokeAllUserTokens(userID string) error
    Close()
}

Error Types

var (
    ErrMissingSecretKey = errors.New("jwt: missing secret key")
    ErrWeakSecretKey    = errors.New("jwt: secret key too weak")
    ErrEmptyUserID      = errors.New("jwt: empty user ID")
    ErrTokenCreation    = errors.New("jwt: failed to create token")
    ErrInvalidToken     = errors.New("jwt: invalid token")
    ErrExpiredToken     = errors.New("jwt: token has expired")
    ErrRevokedToken     = errors.New("jwt: token has been revoked")
    ErrInvalidIssuer    = errors.New("jwt: invalid issuer")
    ErrInvalidAudience  = errors.New("jwt: invalid audience")
    ErrServiceClosed    = errors.New("jwt: service is closed")
)

Testing Support

Clock Injection for Testing

type testClock struct {
    now time.Time
}

func (c *testClock) Now() time.Time {
    return c.now
}

// Create service with test clock
service, err := jwt.New("test-secret-key-with-32-characters!",
    jwt.WithClock(&testClock{now: fixedTime}),
)

License

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

About

Secure, production-ready JWT library for Go with safe revocation, clock skew tolerance, configurable lifetimes, and comprehensive testing

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages