jwt

package
v0.0.0-...-6067653 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2025 License: Apache-2.0 Imports: 11 Imported by: 0

README

JWT Package

A simple, high-performance JWT (JSON Web Token) implementation for Go applications.

Table of Contents

Installation

go get github.com/dmitrymomot/saaskit/pkg/jwt

Overview

The jwt package provides a minimalist JWT implementation focused on type safety, performance, and security. It supports token generation, validation, and HTTP middleware integration without external dependencies. The package is thread-safe and suitable for concurrent use in production applications.

Features

  • Generate and parse JWT tokens with standard or custom claims
  • Type-safe claims with proper validation
  • HTTP middleware with flexible token extraction
  • Support for token expiration and custom claims validation
  • Minimal dependencies with optimized performance
  • HMAC-SHA256 (HS256) signing method
  • Thread-safe implementation for concurrent usage

Usage

Basic Token Generation and Parsing
import (
    "fmt"
    "github.com/dmitrymomot/saaskit/pkg/jwt"
    "time"
)

// Create a JWT service
jwtService, err := jwt.New([]byte("your-secret-key"))
if err != nil {
    // Handle error
    panic(fmt.Sprintf("Failed to create JWT service: %v", err))
}

// Create standard claims
claims := jwt.StandardClaims{
    Subject:   "user123",
    Issuer:    "myapp",
    ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
    IssuedAt:  time.Now().Unix(),
}

// Generate a token
token, err := jwtService.Generate(claims)
if err != nil {
    // Handle error
    fmt.Printf("Failed to generate token: %v\n", err)
    return
}
// Output: token is a string like "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwiaXNzIjoibXlhcHAiLCJleHAiOjE2NTQ0NzI4MDAsImlhdCI6MTY1NDM4NjQwMH0.8Uj7PoJuDdnGoDei5XH6b7YjLdkDZ6Gv2eUDbAyRuYM"

// Parse the token
var parsedClaims jwt.StandardClaims
err = jwtService.Parse(token, &parsedClaims)
if err != nil {
    // Handle error
    fmt.Printf("Failed to parse token: %v\n", err)
    return
}
// parsedClaims now contains: {Subject:"user123", Issuer:"myapp", ExpiresAt:1654472800, IssuedAt:1654386400}

// Access individual claims
fmt.Println("User ID:", parsedClaims.Subject)
// Output: User ID: user123
fmt.Println("Token expires at:", time.Unix(parsedClaims.ExpiresAt, 0))
// Output: Token expires at: 2022-06-06 00:00:00 +0000 UTC
Custom Claims
// Define custom claims
type UserClaims struct {
    jwt.StandardClaims
    Name  string   `json:"name,omitempty"`
    Email string   `json:"email,omitempty"`
    Roles []string `json:"roles,omitempty"`
}

// Create custom claims
claims := UserClaims{
    StandardClaims: jwt.StandardClaims{
        Subject:   "user123",
        ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
    },
    Name:  "John Doe",
    Email: "[email protected]",
    Roles: []string{"admin", "user"},
}

// Generate token with custom claims
token, err := jwtService.Generate(claims)
if err != nil {
    fmt.Printf("Failed to generate token: %v\n", err)
    return
}
// Output: token contains all the custom claims encoded in JWT format

// Parse token with custom claims
var parsedClaims UserClaims
err = jwtService.Parse(token, &parsedClaims)
if err != nil {
    fmt.Printf("Failed to parse token: %v\n", err)
    return
}

// Access custom claims
fmt.Println("User:", parsedClaims.Name)
// Output: User: John Doe
fmt.Println("Roles:", parsedClaims.Roles)
// Output: Roles: [admin user]
Error Handling
import (
    "errors"
    "fmt"
    "github.com/dmitrymomot/saaskit/pkg/jwt"
    "time"
)

// Example 1: Handling expired tokens
expiredClaims := jwt.StandardClaims{
    Subject:   "user123",
    ExpiresAt: time.Now().Add(-1 * time.Hour).Unix(), // Expired 1 hour ago
}

expiredToken, _ := jwtService.Generate(expiredClaims)
var parsedClaims jwt.StandardClaims

err := jwtService.Parse(expiredToken, &parsedClaims)
if err != nil {
    switch {
    case errors.Is(err, jwt.ErrExpiredToken):
        // Token has expired
        fmt.Println("Please log in again, your session has expired")
        // Output: Please log in again, your session has expired
    default:
        fmt.Printf("Unknown error: %v\n", err)
    }
}

// Example 2: Handling tampered tokens
tamperedToken := expiredToken + "tampered"
err = jwtService.Parse(tamperedToken, &parsedClaims)
if err != nil {
    switch {
    case errors.Is(err, jwt.ErrInvalidSignature):
        // Token signature is invalid (token was tampered with)
        fmt.Println("Security alert: Invalid token signature")
        // Output: Security alert: Invalid token signature
    case errors.Is(err, jwt.ErrInvalidToken):
        // Malformed token
        fmt.Println("Invalid token format")
        // Output: Invalid token format
    default:
        fmt.Printf("Unknown error: %v\n", err)
    }
}
HTTP Middleware
import (
    "net/http"
    "github.com/dmitrymomot/saaskit/pkg/jwt"
)

// Create JWT middleware
jwtMiddleware := jwt.Middleware(jwtService)

// Create a protected handler
protectedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Get token from context
    token, ok := jwt.GetToken(r.Context())
    if !ok {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }

    // Get claims from context
    claims, ok := jwt.GetClaims[map[string]any](r.Context())
    if !ok {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }

    // Use the claims
    username, _ := claims["sub"].(string)
    w.Write([]byte("Hello, " + username))
    // Output: "Hello, user123" (if token's subject was "user123")
})

// Apply middleware
http.Handle("/protected", jwtMiddleware(protectedHandler))
Type-Safe Claims in Handlers
// Define your claims type
type UserClaims struct {
    jwt.StandardClaims
    Role string `json:"role"`
}

// In your handler
func handler(w http.ResponseWriter, r *http.Request) {
    var userClaims UserClaims
    if err := jwt.GetClaimsAs(r.Context(), &userClaims); err != nil {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }

    // Now you have strongly typed claims
    if userClaims.Role != "admin" {
        http.Error(w, "Forbidden", http.StatusForbidden)
        return
    }

    w.Write([]byte("Welcome, admin!"))
    // Output: "Welcome, admin!" (if token's role was "admin")
}
Custom Token Extraction
// From a cookie
middleware := jwt.MiddlewareWithConfig(jwt.MiddlewareConfig{
    Service:   jwtService,
    Extractor: jwt.CookieTokenExtractor("auth_token"),
})
// Extracts token from the "auth_token" cookie

// From a query parameter
middleware := jwt.MiddlewareWithConfig(jwt.MiddlewareConfig{
    Service:   jwtService,
    Extractor: jwt.QueryTokenExtractor("token"),
})
// Extracts token from the "token" query parameter (e.g., ?token=xyz)

// From a custom header
middleware := jwt.MiddlewareWithConfig(jwt.MiddlewareConfig{
    Service:   jwtService,
    Extractor: jwt.HeaderTokenExtractor("X-API-Token"),
})
// Extracts token from the "X-API-Token" HTTP header

// From the Authorization header (default)
middleware := jwt.MiddlewareWithConfig(jwt.MiddlewareConfig{
    Service:   jwtService,
    Extractor: jwt.BearerTokenExtractor,
})
// Extracts token from the "Authorization" header with "Bearer " prefix
Skip Middleware for Public Routes
middleware := jwt.MiddlewareWithConfig(jwt.MiddlewareConfig{
    Service: jwtService,
    Skip: func(r *http.Request) bool {
        // Skip auth for public endpoints
        return r.URL.Path == "/api/public" || r.URL.Path == "/health"
    },
})
// The middleware will not check for tokens on /api/public or /health paths

Best Practices

  1. Security:

    • Use strong, secret keys (at least 32 bytes) for signing tokens
    • Set appropriate expiration times on tokens
    • Regularly rotate signing keys for long-lived applications
    • Validate all claims before trusting token content
  2. Token Management:

    • Keep tokens as short-lived as possible
    • Implement token refresh mechanisms for longer sessions
    • Store tokens securely on the client (HttpOnly cookies for web apps)
    • Implement token revocation for sensitive applications
  3. Error Handling:

    • Always check for specific error types when parsing tokens
    • Return appropriate HTTP status codes (401 for expired/invalid tokens)
    • Log suspicious activity like invalid signatures (possible tampering)
    • Provide user-friendly messages without exposing internal details
  4. Performance:

    • Keep claims minimal - tokens are passed with every request
    • Use type-safe claim extraction with generics
    • Implement caching strategies for frequently used tokens

API Reference

Types
type Service struct {
    signingKey []byte
}

Implementation of the JWT service for token generation and parsing.

type StandardClaims struct {
    ID        string `json:"jti,omitempty"`
    Subject   string `json:"sub,omitempty"`
    Issuer    string `json:"iss,omitempty"`
    Audience  string `json:"aud,omitempty"`
    ExpiresAt int64  `json:"exp,omitempty"`
    NotBefore int64  `json:"nbf,omitempty"`
    IssuedAt  int64  `json:"iat,omitempty"`
}

Standard claims structure as per JWT specification.

type MiddlewareConfig struct {
    Service   *Service
    Extractor TokenExtractorFunc
    Skip      SkipFunc
}

Configuration for JWT middleware.

type TokenExtractorFunc func(*http.Request) (string, error)

Function type for extracting JWT tokens from HTTP requests.

type SkipFunc func(*http.Request) bool

Function type for determining whether to skip middleware.

Functions
func New(signingKey []byte) (*Service, error)

Creates a new JWT service with the given signing key.

func NewFromString(signingKey string) (*Service, error)

Creates a new JWT service from a string signing key.

func Middleware(service *Service) func(http.Handler) http.Handler

Creates HTTP middleware for JWT authentication with default configuration.

func MiddlewareWithConfig(config MiddlewareConfig) func(http.Handler) http.Handler

Creates HTTP middleware for JWT authentication with custom configuration.

func GetToken(ctx context.Context) (string, bool)

Gets the JWT token string from the context.

func GetClaims[T any](ctx context.Context) (T, bool)

Gets claims from context as a strongly typed structure using generics.

func GetClaimsAs[T any](ctx context.Context, claims *T) error

Gets claims from context as a strongly typed structure.

func BearerTokenExtractor(r *http.Request) (string, error)

Extracts a JWT token from the Authorization header with "Bearer " prefix.

func CookieTokenExtractor(cookieName string) TokenExtractorFunc

Creates a token extractor that gets tokens from an HTTP cookie.

func QueryTokenExtractor(paramName string) TokenExtractorFunc

Creates a token extractor that gets tokens from a query parameter.

func HeaderTokenExtractor(headerName string) TokenExtractorFunc

Creates a token extractor that gets tokens from an HTTP header.

Methods
func (s *Service) Generate(claims any) (string, error)

Generates a JWT token with the given claims.

func (s *Service) Parse(tokenString string, claims any) error

Parses a JWT token and returns the claims.

func (c StandardClaims) Valid() error

Checks if the standard claims are valid (expiration, etc.).

Error Types
var ErrInvalidToken = errors.New("invalid token")
var ErrExpiredToken = errors.New("token is expired")
var ErrInvalidSigningMethod = errors.New("invalid signing method")
var ErrMissingSigningKey = errors.New("missing signing key")
var ErrInvalidSigningKey = errors.New("invalid signing key")
var ErrInvalidClaims = errors.New("invalid claims")
var ErrMissingClaims = errors.New("missing claims")
var ErrInvalidSignature = errors.New("invalid signature")
var ErrUnexpectedSigningMethod = errors.New("unexpected signing method")

Documentation

Overview

Package jwt provides utilities for generating, parsing, and validating JSON Web Tokens (JWT) as well as HTTP middleware and context helpers for Go services.

The implementation focuses on the HS256 (HMAC-SHA256) algorithm. A high-level Service type wraps signing and verification while accepting any JSON- serialisable claims structure. StandardClaims is provided as a convenient struct mirroring the RFC 7519 registered fields.

Context helper functions make it easy to attach a token and its claims to a context.Context and retrieve them later in the request lifecycle.

Architecture

  • Service – signs and verifies tokens.
  • context.go – helper functions for working with context.
  • middleware.go – HTTP middleware that extracts a token (from header, cookie, query, or custom header) and injects verified claims into the request context.
  • errors.go – sentinel error values returned by the package.

Usage

import "github.com/dmitrymomot/saaskit/pkg/jwt"

// Initialise the service. svc, err := jwt.NewFromString("super-secret")

if err != nil {
    // handle error
}

// Generate a token.

claims := jwt.StandardClaims{
    Subject:   "123",
    ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
}

token, err := svc.Generate(claims)

// Parse the token back. var parsed jwt.StandardClaims

if err := svc.Parse(token, &parsed); err != nil {
    // handle invalid / expired token
}

// Use middleware in an http.Handler chain. http.Handle("/api", jwt.Middleware(svc)(yourHandler))

Error Handling

Errors such as ErrExpiredToken or ErrInvalidSignature are returned as sentinel variables and can be compared using errors.Is.

Performance Considerations

The package uses only the Go standard library, avoiding external dependencies and allocations where possible. Signing keys are kept in memory only. No reflection is used during normal operation.

Index

Constants

View Source
const (
	HeaderType      = "JWT"
	HeaderAlgorithm = "HS256" // HMAC-SHA256 chosen for security/performance balance
)

JWT header constants required by RFC 7519

Variables

View Source
var (
	ErrInvalidToken            = errors.New("jwt: invalid token")
	ErrExpiredToken            = errors.New("jwt: token is expired")
	ErrInvalidSigningMethod    = errors.New("jwt: invalid signing method")
	ErrMissingSigningKey       = errors.New("jwt: missing signing key")
	ErrInvalidSigningKey       = errors.New("jwt: invalid signing key")
	ErrInvalidClaims           = errors.New("jwt: invalid claims")
	ErrMissingClaims           = errors.New("jwt: missing claims")
	ErrInvalidSignature        = errors.New("jwt: invalid signature")
	ErrUnexpectedSigningMethod = errors.New("jwt: unexpected signing method")
)

Functions

func BearerTokenExtractor

func BearerTokenExtractor(r *http.Request) (string, error)

BearerTokenExtractor extracts JWT tokens from "Authorization: Bearer <token>" headers. This is the most common JWT transport method per RFC 6750.

func GetClaims

func GetClaims[T any](ctx context.Context) (T, bool)

GetClaims retrieves JWT claims from context with type safety. Returns zero value and false if claims are missing or of wrong type.

func GetClaimsAs

func GetClaimsAs[T any](ctx context.Context, claims *T) error

GetClaimsAs converts JWT claims from context to the specified type. Uses direct type assertion first, then JSON marshaling for type conversion. This flexibility supports both map[string]any and custom struct claims.

func GetToken

func GetToken(ctx context.Context) (string, bool)

GetToken retrieves the JWT token string from the context.

func Middleware

func Middleware(service *Service) func(next http.Handler) http.Handler

Middleware creates JWT middleware with default Bearer token extraction. Validates tokens and injects claims into request context for downstream handlers.

func MiddlewareWithConfig

func MiddlewareWithConfig(config MiddlewareConfig) func(next http.Handler) http.Handler

MiddlewareWithConfig creates JWT middleware with custom configuration.

func SetClaims

func SetClaims(ctx context.Context, claims any) context.Context

SetClaims stores validated JWT claims in the context. Accepts any claims type - typically map[string]any or custom structs.

func SetToken

func SetToken(ctx context.Context, token string) context.Context

SetToken stores the raw JWT token string in the context.

Types

type Header struct {
	Type      string `json:"typ"`
	Algorithm string `json:"alg"`
}

Header represents the JWT header as defined in RFC 7515

type MiddlewareConfig

type MiddlewareConfig struct {
	Service   *Service           // JWT service for token validation
	Extractor TokenExtractorFunc // Token extraction strategy (defaults to Bearer)
	Skip      SkipFunc           // Optional request filter to bypass validation
}

MiddlewareConfig configures JWT middleware behavior.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service handles JWT token generation and validation using HMAC-SHA256. The signing key is kept in memory only and should be cryptographically secure.

func New

func New(signingKey []byte) (*Service, error)

New creates a new JWT service with the provided signing key. The key should be at least 32 bytes for adequate security with HMAC-SHA256.

func NewFromString

func NewFromString(signingKey string) (*Service, error)

NewFromString creates a new JWT service from a string signing key. Convenience wrapper around New() for string-based configuration.

func (*Service) Generate

func (s *Service) Generate(claims any) (string, error)

Generate creates a JWT token with the given claims. Accepts any JSON-serializable claims structure and returns a signed JWT string.

func (*Service) Parse

func (s *Service) Parse(tokenString string, claims any) error

Parse validates a JWT token and unmarshals its claims into the provided structure. Performs cryptographic verification, algorithm validation, and temporal claim checks.

type SkipFunc

type SkipFunc func(r *http.Request) bool

SkipFunc defines a function that determines whether to skip JWT validation for a request.

type StandardClaims

type StandardClaims struct {
	ID        string `json:"jti,omitempty"` // JWT ID - unique identifier for preventing token reuse
	Subject   string `json:"sub,omitempty"` // Subject - typically user ID or entity identifier
	Issuer    string `json:"iss,omitempty"` // Issuer - identifies who issued the token
	Audience  string `json:"aud,omitempty"` // Audience - intended recipient(s) of the token
	ExpiresAt int64  `json:"exp,omitempty"` // Expiration time - Unix timestamp when token expires
	NotBefore int64  `json:"nbf,omitempty"` // Not before - Unix timestamp when token becomes valid
	IssuedAt  int64  `json:"iat,omitempty"` // Issued at - Unix timestamp when token was created
}

StandardClaims represents the registered JWT claims defined in RFC 7519 Section 4.1. All fields use Unix timestamps for temporal claims to ensure consistent validation.

func (StandardClaims) Valid

func (c StandardClaims) Valid() error

Valid validates the temporal claims against current time. Zero values are treated as unset (per RFC 7519) and are ignored during validation.

type TokenExtractorFunc

type TokenExtractorFunc func(r *http.Request) (string, error)

TokenExtractorFunc defines a function that extracts a token from an HTTP request.

func CookieTokenExtractor

func CookieTokenExtractor(cookieName string) TokenExtractorFunc

CookieTokenExtractor creates a token extractor for cookie-based JWT transport. Useful for browser applications where Authorization headers aren't practical.

func HeaderTokenExtractor

func HeaderTokenExtractor(headerName string) TokenExtractorFunc

HeaderTokenExtractor creates a token extractor for custom headers. Useful for APIs that use non-standard header names for token transport.

func QueryTokenExtractor

func QueryTokenExtractor(paramName string) TokenExtractorFunc

QueryTokenExtractor creates a token extractor for URL query parameters. Generally discouraged due to token exposure in logs and referrer headers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL