Skip to main content

Overview

The jwt middleware provides JSON Web Token (JWT) authentication. It validates tokens from the Authorization header and makes claims available to handlers. Use it when you need:
  • Stateless API authentication
  • Token-based user sessions
  • Microservices authentication

Installation

import "github.com/go-mizu/mizu/middlewares/jwt"

Quick Start

app := mizu.New()

secret := []byte("your-secret-key-at-least-32-bytes")

// Protect routes with JWT
app.Use(jwt.New(secret))

app.Get("/protected", func(c *mizu.Ctx) error {
    // Access claims
    claims := jwt.GetClaims(c)
    userID := jwt.Subject(c)
    return c.JSON(200, map[string]string{"user": userID})
})

Configuration

Options

OptionTypeDefaultDescription
Secret[]byteRequiredHMAC secret key
TokenLookupstring"header:Authorization"Where to find token
AuthSchemestring"Bearer"Authorization scheme
Issuerstring""Expected token issuer
ErrorHandlerfunc(*mizu.Ctx, error) error-Custom error handler
SuccessHandlerfunc(*mizu.Ctx) error-Called after validation

TokenLookup Formats

  • header:Authorization - From Authorization header
  • query:token - From query parameter
  • cookie:jwt - From cookie

Examples

Basic Usage

secret := []byte("your-secret-key-at-least-32-bytes")
app.Use(jwt.New(secret))

With Issuer Validation

app.Use(jwt.WithOptions(jwt.Options{
    Secret: secret,
    Issuer: "my-app",
}))

Token from Query Parameter

app.Use(jwt.WithOptions(jwt.Options{
    Secret:      secret,
    TokenLookup: "query:token",
    AuthScheme:  "", // No scheme for query params
}))
app.Use(jwt.WithOptions(jwt.Options{
    Secret:      secret,
    TokenLookup: "cookie:auth_token",
}))

Custom Error Handler

app.Use(jwt.WithOptions(jwt.Options{
    Secret: secret,
    ErrorHandler: func(c *mizu.Ctx, err error) error {
        return c.JSON(401, map[string]string{
            "error": "Invalid or expired token",
        })
    },
}))

Accessing Claims

app.Get("/profile", func(c *mizu.Ctx) error {
    // Get all claims
    claims := jwt.GetClaims(c)

    // Get specific claim
    email := claims["email"].(string)

    // Get subject (user ID)
    userID := jwt.Subject(c)

    return c.JSON(200, map[string]any{
        "user_id": userID,
        "email":   email,
    })
})

Protected Group

// Public routes
app.Get("/", publicHandler)

// Protected API routes
api := app.Group("/api")
api.Use(jwt.New(secret))

api.Get("/users", listUsers)
api.Post("/users", createUser)

API Reference

Functions

// New creates JWT middleware with secret
func New(secret []byte) mizu.Middleware

// WithOptions creates JWT middleware with full configuration
func WithOptions(opts Options) mizu.Middleware

// GetClaims returns claims from context
func GetClaims(c *mizu.Ctx) map[string]any

// Subject returns the "sub" claim
func Subject(c *mizu.Ctx) string

Standard Claims

ClaimDescription
subSubject (user ID)
issIssuer
expExpiration time
iatIssued at
audAudience

Technical Details

Token Validation Process

The JWT middleware implements a complete token validation workflow:
  1. Token Extraction: The middleware supports three token sources:
    • Header: Extracts from Authorization header with configurable scheme (default: “Bearer”)
    • Query: Extracts from URL query parameters
    • Cookie: Extracts from HTTP cookies
  2. Token Structure Validation: Validates the token has exactly three parts (header.payload.signature) separated by dots.
  3. Signature Verification: Uses HMAC-SHA256 to verify the token signature:
    • Decodes the signature from base64url encoding
    • Computes expected signature using the provided secret
    • Performs constant-time comparison to prevent timing attacks
  4. Payload Decoding: Decodes the base64url-encoded payload and unmarshals JSON claims.
  5. Claims Validation: Validates standard JWT claims:
    • exp (expiration): Rejects tokens past their expiration time
    • nbf (not before): Rejects tokens used before their valid time
    • iss (issuer): Validates against configured issuer if specified
    • aud (audience): Validates against configured audience list if specified (supports both string and array formats)

Context Storage

Claims are stored in the request context using a private context key. This prevents key collisions and ensures type safety. The GetClaims() and Subject() helper functions provide convenient access to the claims.

Error Handling

The middleware defines specific error types for different failure scenarios:
  • ErrTokenMissing: Returns 401 Unauthorized
  • ErrTokenMalformed, ErrTokenInvalid, ErrTokenExpired, ErrTokenNotYetValid, ErrInvalidScheme, ErrInvalidIssuer, ErrInvalidAudience: Return 403 Forbidden
Custom error handlers can be configured via the ErrorHandler option for application-specific error responses.

Security Considerations

  1. Use strong secrets - At least 32 bytes for HS256
  2. Always validate expiration - The middleware checks exp automatically
  3. Use HTTPS - Never transmit tokens over HTTP
  4. Short expiration times - Use refresh tokens for long sessions
  5. Validate issuer - Prevent tokens from other applications

Best Practices

  • Store secrets in environment variables
  • Use short-lived access tokens with refresh tokens
  • Include only necessary data in claims
  • Validate additional claims in handlers when needed

Testing

The JWT middleware includes comprehensive test coverage for all scenarios:
Test CaseDescriptionExpected Behavior
Valid TokenToken with valid signature and expirationReturns 200 OK, request proceeds
Missing TokenNo Authorization header providedReturns 401 Unauthorized
Invalid TokenMalformed token structureReturns 403 Forbidden
Wrong SecretToken signed with different secretReturns 403 Forbidden, signature validation fails
Expired TokenToken with past expiration timeReturns 403 Forbidden
Valid IssuerToken with matching issuer claimReturns 200 OK
Invalid IssuerToken with non-matching issuerReturns 403 Forbidden
Missing IssuerToken without issuer when requiredReturns 403 Forbidden
GetClaimsExtract all claims from contextReturns map with all token claims
SubjectExtract subject claimReturns “sub” claim value
Query LookupToken in query parameterReturns 200 OK, extracts from query string
Cookie LookupToken in HTTP cookieReturns 200 OK, extracts from cookie
Cookie MissingNo cookie when expectedReturns 401 Unauthorized
Not Before (Invalid)Token with future “nbf” claimReturns 403 Forbidden
Not Before (Valid)Token with past “nbf” claimReturns 200 OK
Valid Audience (String)Token with matching audience stringReturns 200 OK
Valid Audience (Array)Token with matching audience in arrayReturns 200 OK
Invalid AudienceToken with non-matching audienceReturns 403 Forbidden
Missing AudienceToken without audience when requiredReturns 403 Forbidden
GetClaims (No Claims)Call GetClaims without JWT middlewareReturns nil
Subject (No Claims)Call Subject without JWT middlewareReturns empty string
Subject (No Sub Claim)Token without “sub” claimReturns empty string
Invalid SchemeWrong authorization scheme (e.g., “Basic”)Returns 403 Forbidden
Custom Error HandlerCustom error handling functionCalls custom handler with custom status
Custom Auth SchemeNon-default scheme (e.g., “Token”)Returns 200 OK with custom scheme
Malformed PartsToken with wrong number of partsReturns 403 Forbidden
Invalid Base64 SignatureSignature not properly base64 encodedReturns 403 Forbidden
Invalid Base64 PayloadPayload not properly base64 encodedReturns 403 Forbidden
Invalid JSON PayloadPayload is not valid JSONReturns 403 Forbidden
Query MissingNo query parameter when expectedReturns 401 Unauthorized
Panic (No Secret)Options without secretPanics with error message
Panic (Invalid Lookup)Invalid TokenLookup formatPanics with error message