You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase: 2 — Authorization & M2M Priority: P1 — High Estimated Effort: Medium Depends on: Fine-Grained Permissions (#508)
Problem Statement
Authorizer has no API key support. End-users and service integrations cannot create long-lived programmatic access tokens. WorkOS and Clerk both offer API key management. API keys are essential for developer platforms, SaaS integrations, and CLI tools that need to authenticate without interactive OAuth flows.
Current Architecture Context
Authentication currently requires interactive flows (password, OTP, magic link, social OAuth)
JWT access tokens expire in 30 minutes — too short for programmatic use
Refresh tokens last 1 year but require the full OAuth refresh flow
No API key schema or middleware exists
ClientCheckMiddleware validates X-Authorizer-Client-ID header — separate concern from user API keys
Storage: Only the SHA-256 hash is stored. The full key is returned once at creation time. KeyPrefix (ak_7Bx9kLmN) stored for identification in list views.
2. Key Generation & Storage
funcGenerateAPIKey() (plaintextstring, hashstring, prefixstring) {
// Generate 48 bytes of cryptographic randomnessraw:=make([]byte, 48)
crypto_rand.Read(raw)
// Encode as base62 with prefixplaintext="ak_"+base62.Encode(raw)
// Store SHA-256 hash (not bcrypt — API keys are validated on every request, needs to be fast)hashBytes:=sha256.Sum256([]byte(plaintext))
hash=hex.EncodeToString(hashBytes[:])
prefix=plaintext[:11] // "ak_" + first 8 charsreturnplaintext, hash, prefix
}
Why SHA-256 over bcrypt: API keys are validated on every API request. Bcrypt's deliberate slowness (100ms+) would add unacceptable latency per request. SHA-256 is sufficient because API keys have high entropy (48 random bytes = 384 bits), making brute force infeasible even with fast hashing. This is the same approach used by GitHub, Stripe, and AWS.
3. Authentication Middleware
New middleware: internal/http_handlers/api_key_middleware.go
API keys are accepted via Authorization: Bearer ak_... header. The middleware intercepts requests before they reach the GraphQL resolver:
func (h*httpProvider) APIKeyMiddleware() gin.HandlerFunc {
returnfunc(c*gin.Context) {
authHeader:=c.GetHeader("Authorization")
if!strings.HasPrefix(authHeader, "Bearer ak_") {
c.Next() // Not an API key, continue to normal authreturn
}
key:=strings.TrimPrefix(authHeader, "Bearer ")
// Hash the provided keyhashBytes:=sha256.Sum256([]byte(key))
keyHash:=hex.EncodeToString(hashBytes[:])
// Look up by hashapiKey, err:=store.GetAPIKeyByHash(ctx, keyHash)
iferr!=nil||apiKey==nil||!apiKey.IsActive {
c.AbortWithStatusJSON(401, gin.H{"error": "invalid_api_key"})
return
}
// Check expiryifapiKey.ExpiresAt>0&&apiKey.ExpiresAt<time.Now().Unix() {
c.AbortWithStatusJSON(401, gin.H{"error": "api_key_expired"})
return
}
// Load useruser, err:=store.GetUserByID(ctx, apiKey.UserID)
iferr!=nil||user==nil {
c.AbortWithStatusJSON(401, gin.H{"error": "user_not_found"})
return
}
// Set context with user identity and API key permissionsc.Set("user", user)
c.Set("api_key_id", apiKey.ID)
c.Set("api_key_permissions", parsePermissions(apiKey.Permissions))
c.Set("auth_method", "api_key")
// Update last_used_at (async, non-blocking)gostore.UpdateAPIKeyLastUsed(ctx, apiKey.ID, c.ClientIP())
c.Next()
}
}
Permission enforcement: When auth_method == "api_key", GraphQL resolvers check that the API key's permissions include the required permission for the operation. API key permissions are a subset of the user's permissions — a key cannot grant more access than its owner has.
--enable-api-keys=true # Enable API key feature
--api-key-max-per-user=25 # Maximum keys per user
--api-key-max-expiry-days=365 # Maximum expiry duration (0 = unlimited)
--api-key-cleanup-interval=24h # How often to clean up expired keys
Migration Strategy
Create api_keys table/collection across all DB providers
Add storage interface methods
Add API key middleware to Gin chain (before auth handlers, after rate limiting)
Add GraphQL types and resolvers
No changes to existing JWT-based auth flow
Testing Plan
Integration test: create key → use key to authenticate → verify user context
Test expired key rejection
Test revoked key rejection
Test permission enforcement (key can't access beyond its permissions)
RFC: API Key Management
Phase: 2 — Authorization & M2M
Priority: P1 — High
Estimated Effort: Medium
Depends on: Fine-Grained Permissions (#508)
Problem Statement
Authorizer has no API key support. End-users and service integrations cannot create long-lived programmatic access tokens. WorkOS and Clerk both offer API key management. API keys are essential for developer platforms, SaaS integrations, and CLI tools that need to authenticate without interactive OAuth flows.
Current Architecture Context
ClientCheckMiddlewarevalidatesX-Authorizer-Client-IDheader — separate concern from user API keysProposed Solution
1. API Key Schema
New schema:
internal/storage/schemas/api_key.goKey format:
ak_prefix + 48-char cryptographically random base62 stringExample:
ak_7Bx9kLmN3pQrStUvWxYz1234567890AbCdEfGhIjKlStorage: Only the SHA-256 hash is stored. The full key is returned once at creation time.
KeyPrefix(ak_7Bx9kLmN) stored for identification in list views.2. Key Generation & Storage
Why SHA-256 over bcrypt: API keys are validated on every API request. Bcrypt's deliberate slowness (100ms+) would add unacceptable latency per request. SHA-256 is sufficient because API keys have high entropy (48 random bytes = 384 bits), making brute force infeasible even with fast hashing. This is the same approach used by GitHub, Stripe, and AWS.
3. Authentication Middleware
New middleware:
internal/http_handlers/api_key_middleware.goAPI keys are accepted via
Authorization: Bearer ak_...header. The middleware intercepts requests before they reach the GraphQL resolver:Permission enforcement: When
auth_method == "api_key", GraphQL resolvers check that the API key's permissions include the required permission for the operation. API key permissions are a subset of the user's permissions — a key cannot grant more access than its owner has.4. Storage Interface Methods
5. GraphQL API
User-facing (users manage their own API keys):
Admin API:
6. Key Rotation
Users create a new key before revoking the old one (zero-downtime rotation):
create_api_key(name: "billing-service-v2", ...)→ new keyrevoke_api_key(id: "old-key-id")→ old key deactivatedNo built-in atomic rotation — this is the standard pattern used by AWS, Stripe, and GitHub.
Security Considerations
--api-key-cleanup-interval=24hCLI Configuration Flags
Migration Strategy
api_keystable/collection across all DB providersTesting Plan
References