Secure, configurable, test‑friendly JWT utilities for Go: safe revocation, clock skew tolerance, strong key policy, flexible refresh, and minimal API surface.
go get github.com/simp-lee/jwtpackage 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)
}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()// 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)// 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 oneNotes:
expiresInandextendsInmust be> 0, otherwise you'll getErrTokenCreation.extendsInmust not exceedMaxTokenLifetimeor you'll getErrTokenCreation.- 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.
// 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)Creates a new JWT service with the specified secret key and options.
Security Requirements:
secretKeymust 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)
// 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)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
}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()
}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")
)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}),
)This project is licensed under the MIT License - see the LICENSE file for details.