Skip to main content

Overview

The keyauth middleware provides API key authentication. It validates API keys from headers, query parameters, or cookies. Perfect for machine-to-machine authentication and public APIs. Use it when you need:
  • API key authentication
  • Service-to-service authentication
  • Third-party API access control
  • Simple API protection without user sessions

Installation

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

Quick Start

app := mizu.New()

// Validate against a list of valid keys
app.Use(keyauth.New(keyauth.ValidateKeys(
    "key-abc123",
    "key-def456",
)))

Configuration

Options

OptionTypeDefaultDescription
ValidatorKeyValidator-Function to validate API keys
KeyLookupstring"header:X-API-Key"Where to find the key
AuthSchemestring""Scheme prefix for header lookup
ErrorHandlerfunc(*mizu.Ctx, error) error-Custom error handler

KeyLookup Format

The KeyLookup option uses the format "source:name":
SourceExampleDescription
header"header:X-API-Key"Read from header
query"query:api_key"Read from query parameter
cookie"cookie:api_key"Read from cookie

Examples

Static API Keys

validKeys := keyauth.ValidateKeys(
    "production-key-abc",
    "production-key-xyz",
)

app.Use(keyauth.New(validKeys))

Database Lookup

app.Use(keyauth.New(func(key string) (bool, error) {
    // Look up key in database
    apiKey, err := db.GetAPIKey(key)
    if err != nil {
        if err == sql.ErrNoRows {
            return false, nil // Key not found
        }
        return false, err // Database error
    }

    // Check if key is active and not expired
    if !apiKey.Active || apiKey.ExpiresAt.Before(time.Now()) {
        return false, nil
    }

    return true, nil
}))

Query Parameter

// Accept key via ?api_key=xxx
app.Use(keyauth.WithOptions(keyauth.Options{
    KeyLookup: "query:api_key",
    Validator: func(key string) (bool, error) {
        return key == "valid-key", nil
    },
}))

Multiple Lookup Sources

// Try header first, then query parameter
app.Use(keyauth.WithOptions(keyauth.Options{
    KeyLookup: "header:X-API-Key",
    Validator: func(key string) (bool, error) {
        return validateKey(key)
    },
}))

// Add fallback for query
app.Use(keyauth.WithOptions(keyauth.Options{
    KeyLookup: "query:api_key",
    Validator: func(key string) (bool, error) {
        return validateKey(key)
    },
}))

With Auth Scheme

// Expect "ApiKey xxx" in Authorization header
app.Use(keyauth.WithOptions(keyauth.Options{
    KeyLookup:  "header:Authorization",
    AuthScheme: "ApiKey",
    Validator:  validateKey,
}))

Custom Error Handler

app.Use(keyauth.WithOptions(keyauth.Options{
    Validator: validateKey,
    ErrorHandler: func(c *mizu.Ctx, err error) error {
        if err == keyauth.ErrKeyMissing {
            return c.JSON(401, map[string]string{
                "error":   "API key required",
                "hint":    "Include X-API-Key header",
            })
        }
        return c.JSON(403, map[string]string{
            "error": "Invalid API key",
        })
    },
}))

Access API Key in Handler

func handler(c *mizu.Ctx) error {
    // Get the validated API key
    key := keyauth.Get(c)

    // Log for audit
    c.Logger().Info("API request", "key", key[:8]+"...")

    return c.JSON(200, data)
}

Rate Limiting Per Key

// Custom rate limiter based on API key
func rateLimit() mizu.Middleware {
    return func(next mizu.Handler) mizu.Handler {
        return func(c *mizu.Ctx) error {
            key := keyauth.Get(c)
            if !rateLimiter.Allow(key) {
                return c.Text(429, "Rate limit exceeded")
            }
            return next(c)
        }
    }
}

app.Use(keyauth.New(validateKey))
app.Use(rateLimit())

API Reference

Functions

// New creates middleware with validator
func New(validator KeyValidator) mizu.Middleware

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

// ValidateKeys creates validator for static key list
func ValidateKeys(keys ...string) KeyValidator

Extracting Key

// FromContext extracts API key from context
func FromContext(c *mizu.Ctx) string

// Get is an alias for FromContext
func Get(c *mizu.Ctx) string

Types

// KeyValidator validates an API key
type KeyValidator func(key string) (valid bool, err error)

Error Types

var (
    ErrKeyMissing // API key not found in request
    ErrKeyInvalid // API key validation failed
)

Technical Details

Implementation Overview

The keyauth middleware implements a flexible API key authentication system with support for multiple key sources (headers, query parameters, and cookies) and customizable validation logic. Key Components:
  1. Key Extraction: The middleware parses the KeyLookup option to determine where to extract the API key from:
    • Header extraction with optional auth scheme prefix support
    • Query parameter extraction
    • Cookie extraction
  2. Validation Pipeline:
    • Extract key from the configured source
    • Return 401 Unauthorized if key is missing
    • Execute custom validator function
    • Return 403 Forbidden if validation fails or returns error
    • Store validated key in request context for downstream handlers
  3. Context Storage: Validated keys are stored using a private contextKey type, ensuring type-safe retrieval and preventing context key collisions.
  4. Error Handling: The middleware provides two error handling paths:
    • Custom error handler (if configured)
    • Default handler: 401 for missing keys, 403 for invalid keys or validation errors

Key Extraction Logic

KeyLookup Format: "source:name"

Header Source:
  - Reads from request.Header.Get(name)
  - If AuthScheme is set, strips the scheme prefix
  - Example: "header:Authorization" with AuthScheme="ApiKey"
    "ApiKey abc123" -> "abc123"

Query Source:
  - Reads from URL query parameter
  - Example: "query:api_key" -> ?api_key=value

Cookie Source:
  - Reads from HTTP cookie
  - Example: "cookie:auth_token"

Validator Function

The KeyValidator function signature:
type KeyValidator func(key string) (valid bool, err error)
Return Values:
  • (true, nil) - Key is valid, allow request
  • (false, nil) - Key is invalid, return 403 Forbidden
  • (_, error) - Validation error occurred, return 403 with error

Static Key Validation

The ValidateKeys helper creates a validator using a map for O(1) lookup:
func ValidateKeys(keys ...string) KeyValidator {
    keySet := make(map[string]bool)
    for _, k := range keys {
        keySet[k] = true
    }
    return func(key string) (bool, error) {
        return keySet[key], nil
    }
}

Security Considerations

  1. Key Generation - Use cryptographically secure random keys
  2. Key Storage - Hash keys in database, never store plain text
  3. Key Rotation - Support key rotation without downtime
  4. Scoping - Consider scoped keys with limited permissions
  5. Logging - Log key usage but not full keys

Secure Key Generation

import "crypto/rand"

func generateAPIKey() string {
    b := make([]byte, 32)
    rand.Read(b)
    return "sk_" + hex.EncodeToString(b)
}

Best Practices

  • Use different keys for different environments
  • Implement key expiration
  • Add rate limiting per key
  • Log API key usage for auditing
  • Support multiple keys per user/service
  • Provide key management API

Testing

The keyauth middleware includes comprehensive test coverage for all functionality:
Test CaseDescriptionExpected Behavior
TestNew - valid keyValid API key provided in X-API-Key headerReturns 200 OK, allows request through
TestNew - invalid keyInvalid API key providedReturns 403 Forbidden, blocks request
TestNew - missing keyNo API key providedReturns 401 Unauthorized, blocks request
TestWithOptions_QueryLookupAPI key provided via query parameterReturns 200 OK when key is valid
TestWithOptions_CookieLookupAPI key provided via cookieReturns 200 OK when key is valid
TestWithOptions_AuthSchemeAPI key with “ApiKey” scheme in Authorization headerStrips scheme prefix, validates key, returns 200 OK
TestWithOptions_ErrorHandlerCustom error handler configuredCustom error handler is invoked, returns custom JSON response
TestWithOptions_ValidatorErrorValidator returns error (e.g., database error)Returns 403 Forbidden with error message
TestFromContextRetrieve API key from context after validationReturns the validated API key string
TestGetTest Get() function as alias for FromContext()Both functions return the same key value
TestValidateKeys - key1Static validator with “key1” in allowed listReturns valid=true
TestValidateKeys - key2Static validator with “key2” in allowed listReturns valid=true
TestValidateKeys - key3Static validator with “key3” in allowed listReturns valid=true
TestValidateKeys - key4Static validator with “key4” not in allowed listReturns valid=false
TestValidateKeys - emptyStatic validator with empty stringReturns valid=false
TestWithOptions_Panics - no validatorCreate middleware without validatorPanics with error message
TestWithOptions_Panics - invalid lookupCreate middleware with invalid KeyLookup formatPanics with error message
TestErrorsVerify error constantsErrKeyMissing and ErrKeyInvalid return correct messages

Running Tests

# Run all keyauth tests
go test ./middlewares/keyauth

# Run with coverage
go test -cover ./middlewares/keyauth

# Run specific test
go test -run TestNew ./middlewares/keyauth