API Key Authentication
API Key authentication is a simple and popular way to secure APIs. It involves distributing unique keys to clients who then include these keys in their requests. This method is particularly useful for public APIs where you want to track usage, implement rate limiting, or provide different access levels to different clients.
How API Key Auth Works
API Keys can be transmitted in several ways:
- As a header (most common)
- As a query parameter
- In the request body
The most secure method is using headers, typically with a name like X-API-Key
or Authorization.
Implementing API Key Auth in Goa
1. Define the Security Scheme
First, define your API Key security scheme in your design package:
package design
import (
. "goa.design/goa/v3/dsl"
)
// APIKeyAuth defines our security scheme
var APIKeyAuth = APIKeySecurity("api_key", func() {
Description("API key security")
Header("X-API-Key") // Specify header name
})
You can also use query parameters instead of headers:
var APIKeyAuth = APIKeySecurity("api_key", func() {
Description("API key security")
Query("api_key") // Specify query parameter name
})
2. Apply the Security Scheme
Like other security schemes, API Key auth can be applied at different levels:
// API level - applies to all services and methods
var _ = API("secure_api", func() {
Security(APIKeyAuth)
})
// Service level - applies to all methods in the service
var _ = Service("secure_service", func() {
Security(APIKeyAuth)
})
// Method level - applies only to this method
Method("secure_method", func() {
Security(APIKeyAuth)
})
3. Define the Payload
For methods that use API Key auth, include the key in the payload:
Method("getData", func() {
Security(APIKeyAuth)
Payload(func() {
APIKey("api_key", "key", String, func() {
Description("API key for authentication")
Example("abcdef123456")
})
Required("key")
// Additional payload fields
Field(1, "query", String, "Search query")
})
Result(ArrayOf(String))
Error("unauthorized")
HTTP(func() {
GET("/data")
// Map the key to the header
Header("key:X-API-Key")
Response("unauthorized", StatusUnauthorized)
})
})
4. Implement the Security Handler
When Goa generates the code, you’ll need to implement a security handler:
// SecurityAPIKeyFunc implements the authorization logic for API Key auth
func (s *service) APIKeyAuth(ctx context.Context, key string) (context.Context, error) {
// Implement your key validation logic here
valid, err := s.validateAPIKey(key)
if err != nil {
return ctx, err
}
if !valid {
return ctx, genservice.MakeUnauthorized(fmt.Errorf("invalid API key"))
}
// You can add key-specific data to the context
ctx = context.WithValue(ctx, "api_key_id", key)
return ctx, nil
}
func (s *service) validateAPIKey(key string) (bool, error) {
// Implementation of key validation
// This could check against a database, cache, etc.
return key == "valid-key", nil
}
Best Practices for API Key Auth
1. Key Generation
Generate strong, random API keys:
func GenerateAPIKey() string {
// Generate 32 random bytes
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
panic(err)
}
// Encode as base64
return base64.URLEncoding.EncodeToString(bytes)
}
2. Key Storage
Store API keys securely:
- Hash keys before storing them
- Use secure key-value stores or databases
- Implement key rotation mechanisms
Example key storage schema:
CREATE TABLE api_keys (
id UUID PRIMARY KEY,
key_hash VARCHAR(64) NOT NULL,
client_id UUID NOT NULL,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP,
last_used_at TIMESTAMP,
is_active BOOLEAN DEFAULT true
);
4. Key Metadata
Associate metadata with API keys for better control:
type APIKeyMetadata struct {
ClientID string
Plan string // e.g., "free", "premium"
Permissions []string // e.g., ["read", "write"]
ExpiresAt time.Time
}
func (s *service) APIKeyAuth(ctx context.Context, key string) (context.Context, error) {
metadata, err := s.getAPIKeyMetadata(key)
if err != nil {
return ctx, err
}
// Add metadata to context
ctx = context.WithValue(ctx, "api_key_metadata", metadata)
return ctx, nil
}
Example Implementation
Here’s a complete example showing how to implement API Key auth in a Goa service:
package design
import (
. "goa.design/goa/v3/dsl"
)
var APIKeyAuth = APIKeySecurity("api_key", func() {
Description("Authenticate using an API key")
Header("X-API-Key")
})
var _ = API("weather_api", func() {
Title("Weather API")
Description("Weather forecast API with API key authentication")
// Apply API key auth by default
Security(APIKeyAuth)
})
var _ = Service("weather", func() {
Description("Weather forecast service")
Method("forecast", func() {
Description("Get weather forecast")
Payload(func() {
// API key will be automatically included
Field(1, "location", String, "Location to get forecast for")
Field(2, "days", Int, "Number of days to forecast")
Required("location")
})
Result(func() {
Field(1, "location", String, "Location")
Field(2, "forecast", ArrayOf(WeatherDay))
})
HTTP(func() {
GET("/forecast/{location}")
Param("days")
Response(StatusOK)
Response(StatusUnauthorized, func() {
Description("Invalid or missing API key")
})
Response(StatusTooManyRequests, func() {
Description("Rate limit exceeded")
})
})
})
// Public endpoint example
Method("health", func() {
Description("Health check endpoint")
NoSecurity()
Result(String)
HTTP(func() {
GET("/health")
})
})
})
// WeatherDay defines the weather forecast for a single day
var WeatherDay = Type("WeatherDay", func() {
Field(1, "date", String, "Forecast date")
Field(2, "temperature", Float64, "Temperature in Celsius")
Field(3, "conditions", String, "Weather conditions")
Required("date", "temperature", "conditions")
})
Generated Code
Goa generates several components for API Key auth:
Security Types
- Types for API key
- Error types for authentication failures
Middleware
- Extracts API key from request
- Calls your security handler
- Handles authentication errors
OpenAPI Documentation
- Documents security requirements
- Shows API key location (header/query)
- Documents error responses
Common Issues and Solutions
1. Key Not Being Sent
If the API key isn’t being sent correctly, check:
- Header name matches exactly
- Key format is correct
- Client is actually sending the key
2. Performance Considerations
For high-traffic APIs:
- Cache API key validation results
- Use fast key-value stores
- Implement key prefixing for quick invalidation
Example caching implementation:
func (s *service) APIKeyAuth(ctx context.Context, key string) (context.Context, error) {
// Check cache first
if metadata, found := s.cache.Get(key); found {
return context.WithValue(ctx, "api_key_metadata", metadata), nil
}
// Validate key and get metadata
metadata, err := s.validateAPIKey(key)
if err != nil {
return ctx, err
}
// Cache the result
s.cache.Set(key, metadata, time.Minute*5)
return context.WithValue(ctx, "api_key_metadata", metadata), nil
}
Next Steps
- Learn about JWT Authentication
- Explore OAuth2 Authentication
- Read about Security Best Practices