π¬ Extractors
The extractors package provides shared value extraction utilities for Fiber middleware packages. It helps reduce code duplication across middleware packages while ensuring consistent behavior and security practices.
Overviewβ
The github.com/gofiber/fiber/v3/extractors module provides standardized value extraction utilities integrated into Fiber's middleware ecosystem. This approach:
- Reduces Code Duplication: Eliminates redundant extractor implementations across middleware packages
- Ensures Consistency: Maintains identical behavior and security practices across all extractors
- Simplifies Maintenance: Changes to extraction logic only need to be made in one place
- Enables Direct Usage: Middleware can import and use extractors directly
- Improves Performance: Shared, optimized extraction functions reduce overhead
What Are Extractors?β
Extractors are utilities that middleware uses to get values from different parts of HTTP requests:
Available Extractorsβ
FromAuthHeader(authScheme string): Extract from Authorization header with optional schemeFromCookie(key string): Extract from HTTP cookiesFromParam(param string): Extract from URL path parametersFromForm(param string): Extract from form dataFromHeader(header string): Extract from custom HTTP headersFromQuery(param string): Extract from URL query parametersFromCustom(key string, fn func(fiber.Ctx) (string, error)): Define custom extraction logic with metadataChain(extractors ...Extractor): Chain multiple extractors with fallback logicResolve(e Extractor, c fiber.Ctx) (Result, error): Return the value and which extractor supplied it βResult.Value,Result.Key,Result.Source(preferred for security decisions on chains)Extractor.Walk(fn func(Extractor) bool): Visit this extractor and every nested chained extractor, depth first and in chain order, stopping early whenfnreturns falseExtractor.Contains(pred func(Extractor) bool): Check whether this extractor, or any nested chained extractor, matches a predicate
Sources and Errorsβ
Each extractor declares where it reads from in its Source field and which name it reads in Key:
| Extractor | Source | Key |
|---|---|---|
FromHeader | SourceHeader (the zero value of Source) | The header name |
FromAuthHeader | SourceAuthHeader | Authorization; the scheme you pass is kept in AuthScheme |
FromCookie | SourceCookie | The cookie name |
FromParam | SourceParam | The route parameter name |
FromForm | SourceForm | The form field name |
FromQuery | SourceQuery | The query parameter name |
FromCustom | SourceCustom | The key you pass |
Chain | That of the first child | That of the first child (SourceCustom and an empty Key for a chain without children) |
An extractor fails with ErrNotFound when its value is missing or empty, and a chain that re-enters itself fails with ErrChainCycle. A custom Extract function may return any other error. A chain moves on to the next child after an error and returns the last error if no child succeeds.
Extractor Behaviorβ
An empty value counts as missing for every extractor. A few more details matter when the value is a credential:
FromHeadermatches the header name case-insensitively, also whenDisableHeaderNormalizingis on. A header sent on several lines is joined into one value, with a comma and a space between the lines (RFC 9110 Section 5.3), so a repeated token does not equal the token that was issued.FromAuthHeaderreturnsErrNotFoundwhen the request carries more than oneAuthorizationline, however each one is spelled, because the credential would be ambiguous. The accepted format is described under Standards Compliance.FromParamreturns the parameter percent-decoded exactly once, whatever theUnescapePathsetting is: a literal%20sent as%2520comes back as%20. WithUnescapePathoff, a value that cannot be decoded returnsErrNotFound; with it on, the router has already decoded the path, so a malformed escape such as100%comes back as sent.Ctx.Paramsreturns the raw value whenUnescapePathis off.FromFormreads throughCtx.FormValue, which looks in the query string first, then in the url-encoded body, then in the multipart fields. A value in the URL therefore satisfiesFromForm, andResolvestill reportsSourceFormfor it. Do not rely onFromFormalone to keep a value out of URLs.
Extractor Structureβ
Each Extractor contains the fields below (order matches the Go struct). Prefer keyed composite literals when constructing Extractor values. Source-aware extraction is provided by the package function Resolve β it is not a field on Extractor, so existing unkeyed literals keep compiling.
type Extractor struct {
Extract func(fiber.Ctx) (string, error) // Extraction callback
Key string // Parameter/header name
AuthScheme string // Auth scheme (FromAuthHeader)
Chain []Extractor // Chained extractors (introspection copy)
Source Source // Declared/static source metadata
}
Resolve reports provenance as a Result, which carries metadata only:
type Result struct {
Value string // The extracted value
Key string // The parameter/header/cookie name the winning extractor read
Source Source // The kind of source the winning extractor read from
Resolved bool // Whether Key and Source name the extractor that actually produced Value
}
Resolved is false when a chain's own Extract answered without any child being
observed to win. Key and Source are then the chain's declared metadata (its
first child) and say nothing about where the value came from, so a security
decision β such as writing a value back to where it was read from β must refuse
rather than trust them.
- Headers:
Authorization,X-API-Key, custom headers - Cookies: Session cookies, authentication tokens
- Query Parameters: URL parameters like
?token=abc123 - Form Data: Form fields of a url-encoded or multipart body (
FromFormalso checks the query string, see Extractor Behavior) - URL Parameters: Route parameters like
/users/:id
Source-Aware Extractionβ
Extractor.Source is declared/static metadata, not an authoritative origin signal by itself:
- For a chain, static
Sourceis the first child. SourceHeaderis the zero value ofSource. A legacyExtract-only callback that omitsSourcetherefore reportsSourceHeaderviaResolve.- Error paths may return static or last-child source metadata even when no value was supplied. Use the returned source for security decisions only when
err == nil, and only when that source metadata is known to be accurate for your extractor.
When middleware needs to know which extractor actually produced the value β to apply a stricter policy, or to write the value back to where it came from β call the package function Resolve:
tokenExtractor := extractors.Chain(
extractors.FromHeader("X-API-Key"),
extractors.FromQuery("api_key"),
)
res, err := extractors.Resolve(tokenExtractor, c)
if err != nil {
return err
}
// res.Value is the extracted value.
// res.Source is SourceHeader or SourceQuery depending on which child won β do
// not assume tokenExtractor.Source (always SourceHeader for this chain).
// res.Key is the header or query parameter name that child read.
if res.Source == extractors.SourceQuery {
// e.g. apply stricter policy or audit logging for query-sourced secrets
}
Resolve always uses the public Extract callback when set (so leaf and chain-level Extract overrides for validation/normalization are honored). For a built-in Chain, that single Extract pass records the winning child and Resolve reads the capture β children are not extracted a second time. If Extract is fully replaced without delegating to the original (no capture), the declared static metadata is returned rather than re-walking Chain children. Leaves return Extract's result with the static metadata. There is no second callback field to clear or re-point.
When Extract is nil, Resolve walks the extractor's own Chain instead, nested chains included. An extractor with neither returns ErrNotFound.
Chain Behaviorβ
The Chain function creates extractors that try multiple sources in order:
- Returns the first successful extraction (non-empty value with no error)
- If all extractors fail, returns the last error encountered or
ErrNotFound - Robust error handling: Skips children with a
nilExtract(and zero-value trailing children) - Cycle prevention: Detects recursive chain re-entry and returns
ErrChainCycle(shared acrossExtractandResolve) - Preserves
SourceandKeyfrom the first extractor for static metadata (notAuthScheme) - On success with
Resolvedtrue,Resolvereports the winning child'sKeyandSourceat runtime - On failure, the returned source is fallback metadata only β do not treat it as the origin of an extracted value
- Exposes a defensive copy of children via the
Chainfield for introspection; mutating that slice does not change which childrenExtractruns - An empty
Chain()always fails withErrNotFound
Chain Introspectionβ
Use Contains to inspect an extractor tree with a predicate:
chain := extractors.Chain(
extractors.FromHeader("X-CSRF-Token"),
extractors.FromCookie("CSRF"),
)
hasCSRFCookie := chain.Contains(func(e extractors.Extractor) bool {
return e.Source == extractors.SourceCookie && e.Key == "CSRF"
})
Walk visits every extractor in the tree, depth first and in chain order, starting with the extractor itself. Prefer Walk and Contains to ranging over the Chain field, which lists only direct children and judges a nested chain by its declared metadata (its first child) instead of by what is inside it. A chain is visited too, so skip entries with a non-empty Chain when you are looking for the extractors that actually read a value:
var cookies []extractors.Extractor
chain.Walk(func(e extractors.Extractor) bool {
if len(e.Chain) == 0 && e.Source == extractors.SourceCookie {
cookies = append(cookies, e)
}
return true // keep walking; return false to stop early
})
Why Middleware Uses Extractorsβ
Middleware needs to extract values from requests for authentication, authorization, and other purposes. Extractors provide:
- Security Awareness: Different sources have different security implications
- Fallback Support: Try multiple sources if the first one doesn't have the value
- Consistency: Same extraction logic across all middleware packages
- Source Tracking: Know where values came from for security decisions
Usage Examplesβ
Basic Usageβ
keyauth.New requires a Validator and panics at startup without one. The keyauth snippets in this guide use this validator:
import (
"crypto/sha256"
"crypto/subtle"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/keyauth"
)
var apiKey = "correct-horse-battery-staple"
func validateAPIKey(c fiber.Ctx, key string) (bool, error) {
// Hash both values so the comparison takes the same time whatever their length
hashedAPIKey := sha256.Sum256([]byte(apiKey))
hashedKey := sha256.Sum256([]byte(key))
if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 {
return true, nil
}
return false, keyauth.ErrMissingOrMalformedAPIKey
}
// KeyAuth middleware extracts key from header
app.Use(keyauth.New(keyauth.Config{
Extractor: extractors.FromHeader("Middleware-Key"),
Validator: validateAPIKey,
}))
Fallback Chainsβ
// Try multiple sources in order
tokenExtractor := extractors.Chain(
extractors.FromHeader("Middleware-Key"), // Try header first
extractors.FromCookie("middleware_key"), // Then cookie
extractors.FromQuery("middleware_key"), // Finally query param
)
app.Use(keyauth.New(keyauth.Config{
Extractor: tokenExtractor,
Validator: validateAPIKey,
}))
Configuring Middleware That Uses Extractorsβ
Authentication Middlewareβ
// KeyAuth middleware (default: FromAuthHeader)
app.Use(keyauth.New(keyauth.Config{
// Default extracts from Authorization header
// Extractor: extractors.FromAuthHeader("Bearer"),
Validator: validateAPIKey,
}))
// Custom header extraction
app.Use(keyauth.New(keyauth.Config{
Extractor: extractors.FromHeader("X-API-Key"),
Validator: validateAPIKey,
}))
// Multiple sources with secure fallback
app.Use(keyauth.New(keyauth.Config{
Extractor: extractors.Chain(
extractors.FromAuthHeader("Bearer"), // Secure first
extractors.FromHeader("X-API-Key"), // Then custom header
extractors.FromQuery("api_key"), // Least secure last
),
Validator: validateAPIKey,
}))
Session Middlewareβ
// Session middleware (default: FromCookie)
app.Use(session.New(session.Config{
// Default extracts from session_id cookie
// Extractor: extractors.FromCookie("session_id"),
}))
// Custom cookie name
app.Use(session.New(session.Config{
Extractor: extractors.FromCookie("my_session"),
}))
CSRF Middlewareβ
// CSRF middleware (default: FromHeader)
app.Use(csrf.New(csrf.Config{
// Default extracts from X-CSRF-Token header
// Extractor: extractors.FromHeader("X-CSRF-Token"),
}))
// Form-based CSRF (less secure, use only if needed)
app.Use(csrf.New(csrf.Config{
Extractor: extractors.Chain(
extractors.FromHeader("X-CSRF-Token"), // Secure first
extractors.FromForm("_csrf"), // Form fallback
),
}))
Security Considerationsβ
Source Characteristicsβ
Different extraction sources have different security properties and use cases:
Headers (Generally Preferred)β
- Authorization Header: Standard for authentication tokens, widely supported
- Custom Headers: Application-specific, less likely to be logged by default
- Considerations: Can be intercepted without HTTPS, may be stripped by proxies
Cookies (Good for Sessions)β
- Session Cookies: Designed for secure client-side storage
- Considerations: Require proper
Secure,HttpOnly, andSameSiteflags - Best for: Session management, remember-me tokens
Query Parameters (Use Sparingly)β
- Query parameters: Convenient for simple APIs and debugging
- Considerations: Always visible in URLs, logged by servers/proxies, stored in browser history
- Best for: Non-sensitive parameters, public identifiers
Form Data (Context Dependent)β
- POST Bodies: Suitable for form submissions and API requests
- Considerations: Avoid putting sensitive data in query strings; ensure request bodies arenβt logged and use the correct content type
- Query string fallback:
FromFormalso accepts the field from the query string, so a value in the URL satisfies it, and the query string wins when both carry the field - Best for: User-generated content, file uploads
Security Best Practicesβ
- Use HTTPS: Encrypt all traffic to protect extracted values in transit
- Validate Input: Always validate and sanitize extracted values
- Log Carefully: Avoid logging sensitive values from any source
- Choose Appropriate Sources: Match the source to your security requirements
- Test Thoroughly: Verify extraction works in your environment
- Monitor Security: Watch for extraction failures or unusual patterns
Chain Ordering Strategyβ
When using multiple sources, order them by your security preferences:
// Example: Prefer headers, fall back to cookies, then query
extractors.Chain(
extractors.FromAuthHeader("Bearer"), // Standard auth
extractors.FromCookie("auth_token"), // Secure storage
extractors.FromQuery("token"), // Public fallback
)
The "best" source depends on your specific use case, security requirements, and application architecture.
Common Security Issuesβ
Leaky URLsβ
// β DON'T: API keys in URLs (visible in logs, history, bookmarks)
app.Use(keyauth.New(keyauth.Config{
Extractor: extractors.FromQuery("api_key"), // PROBLEMATIC
Validator: validateAPIKey,
}))
// β
DO: API keys in headers (not visible in URLs)
app.Use(keyauth.New(keyauth.Config{
Extractor: extractors.FromHeader("X-API-Key"), // BETTER
Validator: validateAPIKey,
}))
Session Tokens in Query Parametersβ
// β DON'T: Session tokens in URLs (can be bookmarked, leaked)
app.Use(session.New(session.Config{
Extractor: extractors.FromQuery("session"), // PROBLEMATIC
}))
// β
DO: Session tokens in cookies (designed for this purpose)
app.Use(session.New(session.Config{
Extractor: extractors.FromCookie("session_id"), // BETTER
}))
Form-Only CSRF Tokensβ
While the default extractor uses headers, some implementations use form fields, which is fine if you don't have AJAX or API clients:
// β DON'T: CSRF tokens only in forms (breaks AJAX, API calls)
app.Use(csrf.New(csrf.Config{
Extractor: extractors.FromForm("_csrf"), // LIMITED
}))
// β
DO: Header-first with form fallback (works everywhere)
app.Use(csrf.New(csrf.Config{
Extractor: extractors.Chain(
extractors.FromHeader("X-CSRF-Token"), // PREFERRED
extractors.FromForm("_csrf"), // FALLBACK
),
}))
Understanding Trade-offsβ
No extractor is universally "secure" - security depends on:
- Whether you're using HTTPS
- How you configure cookies (Secure, HttpOnly, SameSite flags)
- Your logging and monitoring setup
- The sensitivity of the data being extracted
- Your threat model and security requirements
Choose extractors based on your specific use case and security needs, not blanket "secure" vs "insecure" labels.
Standards Complianceβ
Authorization Header (RFC 9110 & RFC 7235)β
The FromAuthHeader extractor validates the Authorization header format described below with strict security checks, and keeps to the RFCs except for one documented restriction:
RFC 9110 Compliance (Authorization Header Format)β
- Section 11.6.2 Format: Enforces
credentials = auth-scheme SP token68structure - Single space (intentional deviation): RFC 9110 allows
1*SPbetween the auth-scheme and the token. The extractor requires exactly one space, which is what clients send, and rejects anything else, so the parse stays unambiguous - Case-insensitive scheme matching:
Bearer,bearer,BEARERall work correctly - Proper whitespace handling: Rejects tabs between scheme and token (only a single space is allowed)
- token68 credentials only: When a scheme is given, credentials made of
auth-paramlists (such asDigest) are rejected. UseFromAuthHeader("")to read the raw header value instead
RFC 7235 Token68 Validationβ
The extractor implements strict token68 character validation per RFC 7235:
- Allowed characters:
A-Z,a-z,0-9,-,.,_,~,+,/,= - Padding rules:
=characters only allowed at the end of tokens - Security validation: Prevents tokens starting with
=or having non-padding characters after= - Whitespace rejection: Rejects tokens containing spaces, tabs, or any other whitespace
Security Featuresβ
- Header injection prevention: Strict parsing prevents malformed authorization headers from bypassing authentication
- Token validation: Ensures extracted tokens conform to standards, preventing authentication bypass
- Consistent error handling: Returns
ErrNotFoundfor all invalid cases
Examplesβ
// Standard usage - strict validation
extractor := extractors.FromAuthHeader("Bearer")
// β
Valid cases:
// "Bearer abc123" -> "abc123"
// "bearer ABC123" -> "ABC123" (case-insensitive scheme)
// "Bearer token123=" -> "token123=" (valid padding)
// "Bearer token==" -> "token==" (valid multiple padding)
// β Invalid cases (all return ErrNotFound):
// "Bearer abc def" -> rejected (space in token)
// "Bearer abc\tdef" -> rejected (tab in token)
// "Bearer =abc" -> rejected (padding at start)
// "Bearer ab=cd" -> rejected (padding in middle)
// "Bearer token" -> rejected (multiple spaces after scheme)
// "Bearer\ttoken" -> rejected (tab after scheme)
// "Bearertoken" -> rejected (no space after scheme)
// Raw header extraction (no validation)
rawExtractor := extractors.FromAuthHeader("")
// "CustomAuth anything goes here" -> "CustomAuth anything goes here"
Benefitsβ
- Standards Compliance: Follows RFC 9110 and RFC 7235 for the supported format, with the single-space restriction above as its one intentional deviation
- Security Hardening: Prevents common authentication bypass vulnerabilities
- Consistent Behavior: Reliable parsing across different client implementations
- Developer Confidence: Clear validation rules reduce authentication bugs
Troubleshootingβ
Extraction Failsβ
Problem: Middleware returns "value not found" or authentication fails
Solutions:
- Check if the expected header/cookie/query parameter is present
- Verify the key name matches exactly (headers are case-insensitive; params/cookies/query keys are case-sensitive)
- Ensure the request uses the correct HTTP method (GET vs POST)
- Check if middleware is configured with the right extractor
Debug Example:
// Add simple debug logging (avoid logging secrets in production)
app.Use(func(c fiber.Ctx) error {
hdr := c.Get("X-API-Key")
cookie := c.Cookies("session_id")
if hdr != "" || cookie != "" {
log.Printf("debug: X-API-Key present=%t, session_id present=%t", hdr != "", cookie != "")
}
return c.Next()
})
Wrong Source Usedβ
Problem: Values extracted from unexpected sources
Solutions:
- Check middleware configuration order
- Verify chain order (first successful extraction wins)
- Use more specific extractors when needed
Security Warningsβ
Problem: Getting security warnings in logs
Solutions:
- Switch to more secure sources (headers/cookies)
- Use HTTPS to encrypt traffic
- Review if sensitive data should be in that source
Advanced Usageβ
Custom Extraction Logicβ
Extractors support custom extractors for complex scenarios:
// Extract from custom logic (rarely needed)
customExtractor := extractors.FromCustom("my-source", func(c fiber.Ctx) (string, error) {
// Complex extraction logic
if value := c.Locals("computed_token"); value != nil {
return value.(string), nil
}
return "", extractors.ErrNotFound
})
Custom extractors break source awareness. When you use FromCustom, middleware cannot determine where the value came from, which means:
- No automatic security warnings for potentially insecure sources
- No source-based logging or monitoring capabilities
- Developer responsibility for ensuring the extraction is secure and appropriate
Only use FromCustom when:
- Standard extractors don't meet your needs
- You've carefully evaluated the security implications
- You're confident in the security of your custom extraction logic
- You understand that middleware cannot provide source-aware security guidance
Note: If you pass nil as the function parameter, FromCustom will return an extractor that always fails with ErrNotFound.
Multiple Middleware Coordinationβ
When using multiple middleware that extract values, ensure they don't conflict:
// Good: Different sources for different purposes
app.Use(keyauth.New(keyauth.Config{
Extractor: extractors.FromHeader("X-API-Key"),
Validator: validateAPIKey,
}))
app.Use(session.New(session.Config{
Extractor: extractors.FromCookie("session_id"),
}))
// Avoid: Same source for different middleware
app.Use(keyauth.New(keyauth.Config{
Extractor: extractors.FromCookie("token"), // API auth
Validator: validateAPIKey,
}))
app.Use(session.New(session.Config{
Extractor: extractors.FromCookie("token"), // Session - CONFLICT!
}))