KeyAuth middleware provides API key based authentication for your Fiber application. It validates keys from various sources (headers, cookies, query parameters, forms) and supports custom validation logic.
Installation
go get -u github.com/gofiber/fiber/v3
go get -u github.com/gofiber/fiber/v3/middleware/keyauth
Signatures
func New(config Config) fiber.Handler
func TokenFromContext(ctx any) string
Usage
Basic Usage with Cookie
package main
import (
"crypto/sha256"
"crypto/subtle"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/keyauth"
"github.com/gofiber/fiber/v3/extractors"
)
var apiKey = "correct horse battery staple"
func validateAPIKey(c fiber.Ctx, key string) (bool, error) {
hashedAPIKey := sha256.Sum256([]byte(apiKey))
hashedKey := sha256.Sum256([]byte(key))
if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 {
return true, nil
}
return false, keyauth.ErrMissingOrMalformedAPIKey
}
func main() {
app := fiber.New()
app.Use(keyauth.New(keyauth.Config{
Extractor: extractors.FromCookie("access_token"),
Validator: validateAPIKey,
}))
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Successfully authenticated!")
})
app.Listen(":3000")
}
app.Use(keyauth.New(keyauth.Config{
// Default extractor uses Authorization: Bearer <token>
Validator: validateAPIKey,
}))
app.Use(keyauth.New(keyauth.Config{
Extractor: extractors.FromHeader("X-API-Key"),
Validator: validateAPIKey,
}))
app.Use(keyauth.New(keyauth.Config{
Extractor: extractors.Chain(
extractors.FromHeader("X-API-Key"),
extractors.FromQuery("api_key"),
extractors.FromCookie("api_token"),
),
Validator: validateAPIKey,
}))
Protect Specific Routes
app.Use(keyauth.New(keyauth.Config{
Next: func(c fiber.Ctx) bool {
// Skip authentication for public routes
return c.Path() == "/" || c.Path() == "/public"
},
Extractor: extractors.FromHeader("X-API-Key"),
Validator: validateAPIKey,
}))
Apply to Specific Handlers
authMiddleware := keyauth.New(keyauth.Config{
Validator: validateAPIKey,
})
app.Get("/public", func(c fiber.Ctx) error {
return c.SendString("Public route")
})
app.Get("/protected", authMiddleware, func(c fiber.Ctx) error {
return c.SendString("Protected route")
})
Configuration
Next
func(fiber.Ctx) bool
default:"nil"
Function to skip this middleware when it returns true.
SuccessHandler
fiber.Handler
default:"c.Next()"
Handler executed when the API key is valid.
ErrorHandler
fiber.ErrorHandler
default:"401 response"
Handler executed when the API key is invalid. Returns 401 with WWW-Authenticate header by default.
Validator
func(fiber.Ctx, string) (bool, error)
required
Required. Function to validate the API key. Must return true, nil for valid keys.
Protected area name used in the WWW-Authenticate header.
Challenge
string
default:"(see description)"
WWW-Authenticate header value when no Authorization scheme is present.
RFC 6750 error code for Bearer challenges: invalid_request, invalid_token, or insufficient_scope.
Human-readable error description for Bearer challenges. Requires Error to be set.
URI with error information for Bearer challenges. Must be absolute and requires Error.
Space-delimited scopes for Bearer challenges. Requires Error set to insufficient_scope.
Default Configuration
var ConfigDefault = Config{
SuccessHandler: func(c fiber.Ctx) error {
return c.Next()
},
ErrorHandler: func(c fiber.Ctx, _ error) error {
return c.Status(fiber.StatusUnauthorized).SendString(ErrMissingOrMalformedAPIKey.Error())
},
Realm: "Restricted",
Extractor: extractors.FromAuthHeader("Bearer"),
}
Best Practices
Use Constant-Time Comparison
import "crypto/subtle"
func validateAPIKey(c fiber.Ctx, key string) (bool, error) {
// Hash both keys
hashedAPIKey := sha256.Sum256([]byte(expectedKey))
hashedKey := sha256.Sum256([]byte(key))
// Use constant-time comparison to prevent timing attacks
if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 {
return true, nil
}
return false, keyauth.ErrMissingOrMalformedAPIKey
}
Database-Backed Validation
func validateAPIKey(c fiber.Ctx, key string) (bool, error) {
// Look up key in database
user, err := db.GetUserByAPIKey(key)
if err != nil {
return false, keyauth.ErrMissingOrMalformedAPIKey
}
// Store user in context for later use
c.Locals("user", user)
return true, nil
}
Custom Error Responses
app.Use(keyauth.New(keyauth.Config{
Validator: validateAPIKey,
ErrorHandler: func(c fiber.Ctx, err error) error {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Invalid API key",
"code": "INVALID_API_KEY",
})
},
}))
Common Patterns
Route-Based Protection
import "regexp"
var protectedURLs = []*regexp.Regexp{
regexp.MustCompile("^/api/"),
regexp.MustCompile("^/admin/"),
}
func shouldAuthenticate(c fiber.Ctx) bool {
path := c.Path()
for _, pattern := range protectedURLs {
if pattern.MatchString(path) {
return false // Don't skip, authenticate
}
}
return true // Skip authentication
}
app.Use(keyauth.New(keyauth.Config{
Next: shouldAuthenticate,
Validator: validateAPIKey,
}))
Multiple API Keys
var validKeys = map[string]bool{
"key1-hash": true,
"key2-hash": true,
"key3-hash": true,
}
func validateAPIKey(c fiber.Ctx, key string) (bool, error) {
hashedKey := sha256.Sum256([]byte(key))
keyStr := hex.EncodeToString(hashedKey[:])
if validKeys[keyStr] {
return true, nil
}
return false, keyauth.ErrMissingOrMalformedAPIKey
}
Rate Limiting by API Key
func validateAPIKey(c fiber.Ctx, key string) (bool, error) {
user, err := db.GetUserByAPIKey(key)
if err != nil {
return false, keyauth.ErrMissingOrMalformedAPIKey
}
// Store user for rate limiter
c.Locals("user_id", user.ID)
return true, nil
}
// Use user_id in rate limiter key generator
app.Use(limiter.New(limiter.Config{
KeyGenerator: func(c fiber.Ctx) string {
if userID, ok := c.Locals("user_id").(int); ok {
return fmt.Sprintf("user:%d", userID)
}
return c.IP()
},
}))
Retrieve Token in Handler
app.Get("/profile", func(c fiber.Ctx) error {
token := keyauth.TokenFromContext(c)
user := getUserFromToken(token)
return c.JSON(user)
})
Testing
# Missing API key
curl http://localhost:3000/protected
# 401 Unauthorized
# Valid API key in header
curl -H "Authorization: Bearer correct-horse-battery-staple" http://localhost:3000/protected
# Successfully authenticated!
# Valid API key in custom header
curl -H "X-API-Key: my-secret-key" http://localhost:3000/protected
# Valid API key in query
curl http://localhost:3000/protected?api_key=my-secret-key
# Valid API key in cookie
curl --cookie "access_token=my-secret-key" http://localhost:3000/protected
KeyAuth uses the shared extractors package. Common extractors:
extractors.FromAuthHeader("Bearer") - Authorization header
extractors.FromHeader("X-API-Key") - Custom header
extractors.FromQuery("api_key") - Query parameter
extractors.FromCookie("token") - Cookie
extractors.FromForm("api_key") - Form field
extractors.Chain(...) - Try multiple sources
extractors.FromCustom(func) - Custom logic
See the Extractors Guide for details.