Time Of Check Time Of Use in Fiber with Api Keys
Time Of Check Time Of Use in Fiber with Api Keys — how this specific combination creates or exposes the vulnerability
Time Of Check Time Of Use (TOCTOU) is a class of race condition where the state of a resource changes between a security check and the subsequent use of that resource. In the Fiber web framework for Go, this commonly arises when an API key is validated early in a request lifecycle—often in middleware—and then the handler relies on that validation without rechecking contextual assumptions immediately before performing a sensitive action.
Consider a route that first authenticates a request using an API key via middleware, attaches a user identity to the context, and then later performs an authorization decision such as "can this key modify this resource?" If the authorization check reads stale data (e.g., a cached role, permission, or resource ownership) instead of re-querying the current state, an attacker can exploit the window between check and use. For example, an API key might initially be valid and scoped to read-only operations, but the key’s permissions could be updated or revoked after the middleware runs. If the handler skips re-verifying scope right before performing a write, a malicious request can proceed with elevated privileges despite a revoked or restricted key.
Another scenario specific to Fiber involves route parameters and key binding. A middleware can validate an API key and extract an associated tenant or user ID, then pass that ID to downstream logic. If the handler later constructs resource identifiers (e.g., /resources/{id}) using parameters from the request without confirming that the authenticated key still maps to that identifier, an attacker can supply a different {id} that points to another tenant’s data. Because the check only happened once at the middleware layer, there’s no guarantee that the key and the resource identifier remain consistent at the time of use, enabling unauthorized data access or modification (a BOLA/IDOR pattern).
These issues are exacerbated when API keys are long-lived or cached for performance. A key that is validated once and stored in a request-scoped context may become outdated if permissions change concurrently. Fiber’s chain of handlers means that multiple pieces of code can run sequentially; if any handler assumes the key’s attributes are immutable after the initial check, the system becomes vulnerable. Real-world parallels include insecure direct object references where an attacker manipulates identifiers to access objects they should not, and the missing re-validation effectively turns an authorization check into a one-time formality rather than a per-action guarantee.
Api Keys-Specific Remediation in Fiber — concrete code fixes
To mitigate TOCTOU with API keys in Fiber, ensure authorization is re-evaluated immediately before each sensitive operation using fresh data rather than cached context. Prefer binding key-to-permissions at the point of use and avoid storing derived authorization state in request context that is reused across multiple handlers.
Example 1: Re-validate scope immediately before a write operation.
import (
"github.com/gofiber/fiber/v2"
"net/http"
)
type KeyService interface {
GetKeyPermissions(key string) ([]string, error)
}
func RequirePermission(perm string, svc KeyService) fiber.Handler {
return func(c *fiber.Ctx) error {
key := c.Get("X-API-Key") // extracted earlier by auth middleware
perms, err := svc.GetKeyPermissions(key)
if err != nil || !hasPermission(perms, perm) {
return c.Status(http.StatusForbidden).SendString("insufficient permissions")
}
return c.Next()
}
}
func UpdateResourceHandler(svc KeyService) fiber.Handler {
return func(c *fiber.Ctx) error {
// Re-validate right before the operation
if err := RequirePermission("write:resource")(svc)(c); err != nil {
return err
}
// Proceed with safe write
return c.SendString("updated")
}
}
Example 2: Avoid storing user/tenant in context after initial key validation; instead, resolve it again using the key right before accessing tenant-specific resources.
type TenantResolver interface {
ResolveTenant(key string) (string, error)
}
func Handler(resolver TenantResolver) fiber.Handler {
return func(c *fiber.Ctx) error {
key := c.Get("X-API-Key")
tid, err := resolver.ResolveTenant(key)
if err != nil {
return c.Status(http.StatusUnauthorized).SendString("invalid key")
}
// Use tid immediately for the operation; do not rely on a previously stored value
resource, err := loadResource(c.Params("id"), tid)
if err != nil {
return c.Status(http.StatusNotFound).SendString("not found")
}
return c.JSON(resource)
}
}
Example 3: Enforce key binding by including key metadata in resource identifiers or using short-lived scoped tokens to reduce the window of inconsistency. Combine with strict parameter validation to ensure the resolved identifier matches the key’s allowed set.
| Approach | When to use | Trade-offs |
|---|---|---|
| Re-query permissions before each action | Dynamic permissions, frequent updates | Higher latency due to repeated checks |
| Short-lived scoped tokens bound to key | High-throughput, reduced re-checks | Increased token issuance overhead |
| Strict parameter-to-key binding checks | Multi-tenant isolation | Requires careful ID design |