> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Key Auth

> API key authentication middleware for securing APIs.

## 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

```go theme={null}
import "github.com/go-mizu/mizu/middlewares/keyauth"
```

## Quick Start

```go theme={null}
app := mizu.New()

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

## Configuration

### Options

| Option         | Type                           | Default              | Description                     |
| -------------- | ------------------------------ | -------------------- | ------------------------------- |
| `Validator`    | `KeyValidator`                 | -                    | Function to validate API keys   |
| `KeyLookup`    | `string`                       | `"header:X-API-Key"` | Where to find the key           |
| `AuthScheme`   | `string`                       | `""`                 | Scheme prefix for header lookup |
| `ErrorHandler` | `func(*mizu.Ctx, error) error` | -                    | Custom error handler            |

### KeyLookup Format

The `KeyLookup` option uses the format `"source:name"`:

| Source   | Example              | Description               |
| -------- | -------------------- | ------------------------- |
| `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

```go theme={null}
validKeys := keyauth.ValidateKeys(
    "production-key-abc",
    "production-key-xyz",
)

app.Use(keyauth.New(validKeys))
```

### Database Lookup

```go theme={null}
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

```go theme={null}
// 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

```go theme={null}
// 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

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

### Custom Error Handler

```go theme={null}
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

```go theme={null}
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

```go theme={null}
// 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

```go theme={null}
// 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

```go theme={null}
// 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

```go theme={null}
// KeyValidator validates an API key
type KeyValidator func(key string) (valid bool, err error)
```

### Error Types

```go theme={null}
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:

```go theme={null}
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:

```go theme={null}
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

```go theme={null}
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 Case                                    | Description                                          | Expected Behavior                                             |
| -------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------- |
| **TestNew - valid key**                      | Valid API key provided in X-API-Key header           | Returns 200 OK, allows request through                        |
| **TestNew - invalid key**                    | Invalid API key provided                             | Returns 403 Forbidden, blocks request                         |
| **TestNew - missing key**                    | No API key provided                                  | Returns 401 Unauthorized, blocks request                      |
| **TestWithOptions\_QueryLookup**             | API key provided via query parameter                 | Returns 200 OK when key is valid                              |
| **TestWithOptions\_CookieLookup**            | API key provided via cookie                          | Returns 200 OK when key is valid                              |
| **TestWithOptions\_AuthScheme**              | API key with "ApiKey" scheme in Authorization header | Strips scheme prefix, validates key, returns 200 OK           |
| **TestWithOptions\_ErrorHandler**            | Custom error handler configured                      | Custom error handler is invoked, returns custom JSON response |
| **TestWithOptions\_ValidatorError**          | Validator returns error (e.g., database error)       | Returns 403 Forbidden with error message                      |
| **TestFromContext**                          | Retrieve API key from context after validation       | Returns the validated API key string                          |
| **TestGet**                                  | Test Get() function as alias for FromContext()       | Both functions return the same key value                      |
| **TestValidateKeys - key1**                  | Static validator with "key1" in allowed list         | Returns valid=true                                            |
| **TestValidateKeys - key2**                  | Static validator with "key2" in allowed list         | Returns valid=true                                            |
| **TestValidateKeys - key3**                  | Static validator with "key3" in allowed list         | Returns valid=true                                            |
| **TestValidateKeys - key4**                  | Static validator with "key4" not in allowed list     | Returns valid=false                                           |
| **TestValidateKeys - empty**                 | Static validator with empty string                   | Returns valid=false                                           |
| **TestWithOptions\_Panics - no validator**   | Create middleware without validator                  | Panics with error message                                     |
| **TestWithOptions\_Panics - invalid lookup** | Create middleware with invalid KeyLookup format      | Panics with error message                                     |
| **TestErrors**                               | Verify error constants                               | ErrKeyMissing and ErrKeyInvalid return correct messages       |

### Running Tests

```bash theme={null}
# 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
```

## Related Middlewares

* [basicauth](/middlewares/basicauth) - Username/password authentication
* [bearerauth](/middlewares/bearerauth) - Bearer token authentication
* [ratelimit](/middlewares/ratelimit) - Rate limiting
