Refactor KeyAuth Middleware: Extractor-Based Configuration and Enhanced Flexibility#3685
Refactor KeyAuth Middleware: Extractor-Based Configuration and Enhanced Flexibility#3685ReneWerner87 merged 4 commits intomainfrom
Conversation
…pdate Error Handling - Refactored key extraction logic in extractors.go to improve clarity and maintainability. - Added comprehensive unit tests for all extractors, ensuring correct behavior for missing and valid API keys. - Updated error messages for better clarity and consistency across the middleware. - Introduced a new WWW-Authenticate header feature for improved API security compliance. - Enhanced test coverage for configuration defaults and custom configurations in config_test.go. - Improved handling of multiple key sources in keyauth_test.go, ensuring robust validation and extraction.
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughRefactors KeyAuth middleware from KeyLookup/AuthScheme to an Extractor/Validator model with chaining and per-source missing-key errors, updates default config and error handling, sets WWW-Authenticate using realm and scheme, and revises docs (including EncryptCookie key length and CSRF examples). Adds new extractor implementations and comprehensive tests. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant FiberApp
participant KeyAuth as KeyAuth Middleware
participant Extractor
participant Validator
participant ErrorHandler
Client->>FiberApp: HTTP Request
FiberApp->>KeyAuth: Invoke middleware
KeyAuth->>Extractor: Extract(c)
alt Key found
KeyAuth->>Validator: Validate(c, key)
alt Valid
KeyAuth-->>FiberApp: SuccessHandler / next
else Invalid/expired
KeyAuth->>KeyAuth: Set WWW-Authenticate (scheme, realm)
KeyAuth->>ErrorHandler: Handle invalid
ErrorHandler-->>FiberApp: 401 response
end
else Missing key
KeyAuth->>KeyAuth: Set WWW-Authenticate (scheme, realm)
KeyAuth->>ErrorHandler: Handle missing (per-source error)
ErrorHandler-->>FiberApp: 401 response
end
sequenceDiagram
autonumber
participant C as Context
participant Chain as Chain(Extractor...)
participant E1 as Extractor #1
participant E2 as Extractor #2
participant E3 as Extractor #3
C->>Chain: Extract(c)
Chain->>E1: Extract(c)
alt E1 fails
Chain->>E2: Extract(c)
alt E2 fails
Chain->>E3: Extract(c)
alt E3 fails
Chain-->>C: Return last error
else
Chain-->>C: Return key from E3
end
else
Chain-->>C: Return key from E2
end
else
Chain-->>C: Return key from E1
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Summary of Changes
Hello @sixcolors, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request significantly refactors the keyauth middleware in Fiber, transitioning from a string-based API key extraction mechanism to a more modern, type-safe, and flexible extractor system. The primary goal is to enhance the clarity, extensibility, and composability of API key authentication configurations. This change simplifies how developers specify API key sources and improves error handling, making the middleware more robust and easier to use.
Highlights
- Extractor-Based API Key Retrieval: Introduces a new
Extractorfield in thekeyauth.Configstruct, allowing explicit definition of how API keys are retrieved (e.g., from headers, query parameters, cookies, form data, or path parameters). - Helper Functions for Extractors: Provides a set of built-in helper functions like
FromHeader,FromAuthHeader,FromCookie,FromQuery,FromParam, andFromFormto easily configure theExtractor. - Extractor Chaining: Adds
keyauth.Chainto combine multiple extractors, allowing the middleware to try different sources for the API key in a defined order. - Configuration Simplification and Enforcement: Removes the legacy
KeyLookup,AuthScheme, andCustomKeyLookupfields. TheValidatorfunction is now explicitly required, and the middleware will panic if it's not provided. - Enhanced Error Handling and WWW-Authenticate Header: Updates default error handling for missing/invalid keys and automatically sets the
WWW-Authenticateheader on authentication failure when an auth header extractor is used, with configurableRealmsupport. - Documentation and Test Updates: Comprehensive updates to documentation, including migration steps and examples, along with new tests covering all extractor functionalities and error scenarios.
- Breaking Change: The removal of
KeyLookup,AuthScheme, andCustomKeyLookupmeans existing implementations must migrate to the newExtractorconfiguration.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Pull Request Overview
This PR modernizes the keyauth middleware by replacing the legacy string-based key extraction configuration with a type-safe extractor system. This refactor improves developer experience through explicit configuration and enables flexible composition of multiple extraction sources.
Key Changes
- Introduces a new
Extractorfield with helper functions likeFromHeader,FromAuthHeader,FromCookie, andChainfor type-safe API key extraction - Removes deprecated
KeyLookup,AuthScheme, andCustomKeyLookupfields in favor of the extractor system - Adds automatic
WWW-Authenticateheader setting on authentication failure when using auth header extractors
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| middleware/keyauth/keyauth.go | Core middleware logic updated to use extractor system and handle WWW-Authenticate headers |
| middleware/keyauth/config.go | Configuration refactored to require Validator and use Extractor instead of string-based lookups |
| middleware/keyauth/extractors.go | New file implementing type-safe extractor functions and chaining logic |
| middleware/keyauth/keyauth_test.go | Comprehensive test updates covering all extraction methods and error scenarios |
| middleware/keyauth/extractors_test.go | New tests specifically for extractor functionality and chaining behavior |
| middleware/keyauth/config_test.go | New tests for configuration validation and default value handling |
| docs/middleware/keyauth.md | Updated documentation with migration guide and extractor usage examples |
| docs/whats_new.md | Added migration notes for the breaking changes |
| docs/middleware/encryptcookie.md | Minor documentation update referencing new extractor syntax |
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
| expectedCode = 404 | ||
| expectedBody = "Cannot GET /" | ||
| } | ||
| require.Equal(t, expectedCode, res.StatusCode, test.description) |
There was a problem hiding this comment.
The variable body is used in the next line but is never declared or assigned in this scope. This will cause a compilation error.
| req.Header.Add("Content-Type", "application/x-www-form-urlencoded") | ||
| } | ||
|
|
||
| res, err := app.Test(req, testConfig) |
There was a problem hiding this comment.
The variable body is read from the response but is never assigned. The body, err := io.ReadAll(res.Body) statement appears to be missing before line 196 where body is used.
There was a problem hiding this comment.
Code Review
This pull request is an excellent and comprehensive refactoring of the keyauth middleware. The move from a string-based KeyLookup to a type-safe, composable Extractor system is a significant improvement in terms of flexibility, clarity, and extensibility. The changes are well-supported by thorough updates to documentation and a robust new test suite that covers the new extractors, chaining, and error scenarios. Making the Validator a required field and enhancing the error handling with automatic WWW-Authenticate headers are also great additions that improve the middleware's usability and security posture. Overall, this is a high-quality contribution that modernizes the middleware effectively.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3685 +/- ##
==========================================
+ Coverage 91.91% 91.98% +0.07%
==========================================
Files 113 114 +1
Lines 11496 11494 -2
==========================================
+ Hits 10566 10573 +7
+ Misses 667 661 -6
+ Partials 263 260 -3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
middleware/keyauth/extractors_test.go (1)
161-161: Update the comment to match the actual error being tested.The comment mentions
ErrTokenNotFound, but the test is actually checking forErrMissingAPIKey. Update the comment to reflect what's being tested.- // All extractors find nothing (return empty string and nil error), should return ErrMissingAPIKey + // All extractors find nothing (return empty string and nil error), should return ErrMissingAPIKey
🧹 Nitpick comments (12)
middleware/keyauth/extractors.go (3)
165-176: Trim header values to avoid false negatives due to incidental whitespaceHeaders can contain incidental whitespace from proxies/clients. Trimming improves robustness and makes behavior consistent with
FromAuthHeader.- apiKey := c.Get(param) + apiKey := strings.TrimSpace(c.Get(param)) if apiKey == "" { return "", ErrMissingAPIKeyInHeader } return apiKey, nil
211-252: Propagate AuthScheme metadata in Chain to support WWW-Authenticate
keyauth.Newderives the auth scheme viagetAuthScheme(cfg.Extractor). IfExtractoris a chain, settingAuthSchemefrom the first extractor helps consumers that don't—or can't—introspectChainrecursively.return Extractor{ Extract: func(c fiber.Ctx) (string, error) { var lastErr error @@ }, - Source: primarySource, - Key: primaryKey, - Chain: extractors, + Source: primarySource, + Key: primaryKey, + AuthScheme: extractors[0].AuthScheme, + Chain: extractors, }
41-48: Optional: Introduce an ExtractorFunc type for readability and consistencyDefining a named function type improves readability and makes signatures self-documenting.
type Extractor struct { - Extract func(fiber.Ctx) (string, error) + Extract func(fiber.Ctx) (string, error) Key string // The parameter/header name used for extraction AuthScheme string // The auth scheme, e.g., "Bearer" for AuthHeader Chain []Extractor // For chaining multiple extractors Source Source // The type of source being extracted from }Outside this range, consider:
// near the top of the file type ExtractorFunc func(fiber.Ctx) (string, error) // and then in Extractor: Extract ExtractorFuncdocs/middleware/encryptcookie.md (1)
59-62: Clarify “bytes” vs “characters” and base64 length to avoid misconfigurationThe note currently says “32 char key” while requiring base64-encoded keys of 16/24/32 bytes. That’s ambiguous—base64 strings are longer than the raw byte length. Recommend clarifying wording.
-To generate a 32 char key, use `openssl rand -base64 32` or `encryptcookie.GenerateKey(32)`... +To generate a 32-byte key, use `openssl rand -base64 32` or `encryptcookie.GenerateKey(32)`. Note that the resulting base64 string will be longer than 32 characters, but decodes to 32 bytes.As follow-up, update the example configs to use generated/base64 keys rather than human-readable placeholders:
- In the example at Lines 39-43:
app.Use(encryptcookie.New(encryptcookie.Config{ Key: encryptcookie.GenerateKey(32), }))
- In the CSRF example at Lines 92-99:
app.Use(encryptcookie.New(encryptcookie.Config{ Key: encryptcookie.GenerateKey(32), Except: []string{csrf.ConfigDefault.CookieName}, }))middleware/keyauth/config_test.go (2)
12-20: Panic assertion is fine, but the comment misattributes the causeThe panic arises because no config is provided (len(config) == 0), not because
ConfigDefault.Validatoris nil. The message is the same, but consider adjusting the comment to avoid confusion.-// The New function will call configDefault with no arguments -// which will panic because ConfigDefault.Validator is nil. +// The New function will call configDefault with no arguments, +// which panics due to a missing validator in the provided config.
45-71: Preservation of custom handlers and extractor is well tested; consider adding a test for Extractor metadataYou verify the function pointer for
Extract. Consider also asserting thatKey,Source, and (if applicable)AuthSchemeon the extractor are preserved to guard against future regressions.If helpful, I can draft an additional test case asserting:
cfg.Extractor.Key == "X-API-Key"cfg.Extractor.Source == SourceHeaderdocs/whats_new.md (2)
1096-1096: EncryptCookie key requirements—align wording across docsThis statement is good, but ensure consistency with encryptcookie.md (clarify “bytes” vs “characters” and base64 length). See my suggestion there.
1943-1963: Add a chaining example to demonstrate multi-source fallbacksConsider adding a short example showing
keyauth.Chain(...), which many users will reach for during migration.Example to append below the “After” block:
// After (multiple fallbacks) app.Use(keyauth.New(keyauth.Config{ Extractor: keyauth.Chain( keyauth.FromAuthHeader(fiber.HeaderAuthorization, "Bearer"), keyauth.FromHeader("X-API-Key"), keyauth.FromQuery("api_key"), ), Validator: validateAPIKey, }))middleware/keyauth/extractors_test.go (1)
92-98: Consider consistently releasing contexts immediately after use within the same test.While deferring the release is safe, you're acquiring multiple contexts within the same test function. For clarity and to avoid potential context pool exhaustion in larger tests, consider releasing each context immediately after use instead of deferring.
// FromQuery ctx = app.AcquireCtx(&fasthttp.RequestCtx{}) - defer app.ReleaseCtx(ctx) ctx.Request().SetRequestURI("/?api_key=token_from_query") token, err = FromQuery("api_key").Extract(ctx) require.NoError(t, err) require.Equal(t, "token_from_query", token) + app.ReleaseCtx(ctx)Apply similar changes to lines 100-106, 108-114, and 116-122.
docs/middleware/keyauth.md (2)
216-220: Security consideration: Remove hardcoded API key from documentation.The example contains a hardcoded API key in the curl command. While this is for demonstration purposes, it's better practice to use placeholder values in documentation to avoid accidentally exposing real credentials.
# /allowed needs to be authenticated -curl --header "Authorization: Bearer my-super-secret-key" http://localhost:3000/allowed +curl --header "Authorization: Bearer <your-api-key>" http://localhost:3000/allowed #> Successfully authenticated!
263-263: Grammar issue in the documentation table.There's a minor grammar issue in the description column.
-| Validator | `func(fiber.Ctx, string) (bool, error)` | **Required.** Validator is a function to validate the key. | `nil` (panic) | +| Validator | `func(fiber.Ctx, string) (bool, error)` | **Required.** Validator is a function to validate the key. | `nil` (will panic) |middleware/keyauth/keyauth.go (1)
67-79: Consider documenting the auth scheme detection behavior.The
getAuthSchemefunction searches both the top-level extractor and any chained extractors for an auth header source. This behavior should be documented to clarify that only the first found auth scheme is used.// getAuthScheme inspects an extractor and its chain to find the auth scheme -// used by FromAuthHeader. It returns the scheme, or an empty string if not found. +// used by FromAuthHeader. It returns the first auth scheme found, or an empty string if none is found. +// When multiple FromAuthHeader extractors are chained, only the first one's scheme is used. func getAuthScheme(e Extractor) string {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these settings in your CodeRabbit configuration.
📒 Files selected for processing (9)
docs/middleware/encryptcookie.md(2 hunks)docs/middleware/keyauth.md(8 hunks)docs/whats_new.md(3 hunks)middleware/keyauth/config.go(2 hunks)middleware/keyauth/config_test.go(1 hunks)middleware/keyauth/extractors.go(1 hunks)middleware/keyauth/extractors_test.go(1 hunks)middleware/keyauth/keyauth.go(3 hunks)middleware/keyauth/keyauth_test.go(21 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
docs/**
📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)
Review and update the contents of the
docsfolder if necessary when modifying code
Files:
docs/middleware/encryptcookie.mddocs/whats_new.mddocs/middleware/keyauth.md
🧠 Learnings (9)
📓 Common learnings
Learnt from: dave-gray101
PR: gofiber/fiber#3027
File: docs/api/middleware/keyauth.md:222-222
Timestamp: 2024-10-08T19:06:06.583Z
Learning: The `KeyLookup` field in the `keyauth` middleware does not support pipe-delimited keys. Instead, it specifies a single source and name for key extraction, with additional sources specified in the `FallbackKeyLookups` field.
Learnt from: dave-gray101
PR: gofiber/fiber#3027
File: docs/api/middleware/keyauth.md:222-222
Timestamp: 2024-06-09T18:50:02.075Z
Learning: The `KeyLookup` field in the `keyauth` middleware does not support pipe-delimited keys. Instead, it specifies a single source and name for key extraction, with additional sources specified in the `FallbackKeyLookups` field.
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:20-23
Timestamp: 2024-07-01T03:44:03.672Z
Learning: Unit tests for key length enforcement in both `EncryptCookie` and `DecryptCookie` functions have been added to ensure robust validation and prevent potential runtime errors.
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:20-23
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Unit tests for key length enforcement in both `EncryptCookie` and `DecryptCookie` functions have been added to ensure robust validation and prevent potential runtime errors.
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:51-54
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Unit tests for key length enforcement in `DecryptCookie` have been added to ensure consistency and security in the encryption processes.
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:51-54
Timestamp: 2024-07-01T03:33:22.283Z
Learning: Unit tests for key length enforcement in `DecryptCookie` have been added to ensure consistency and security in the encryption processes.
📚 Learning: 2024-07-01T03:44:03.672Z
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:20-23
Timestamp: 2024-07-01T03:44:03.672Z
Learning: Unit tests for key length enforcement in both `EncryptCookie` and `DecryptCookie` functions have been added to ensure robust validation and prevent potential runtime errors.
Applied to files:
docs/middleware/encryptcookie.mddocs/whats_new.md
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:51-54
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Unit tests for key length enforcement in `DecryptCookie` have been added to ensure consistency and security in the encryption processes.
Applied to files:
docs/middleware/encryptcookie.mddocs/whats_new.md
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:22-25
Timestamp: 2024-10-08T19:06:06.583Z
Learning: The `encryptcookie_test.go` file contains unit tests that validate key lengths for both `EncryptCookie` and `DecryptCookie` functions, ensuring that invalid key lengths raise appropriate errors.
Applied to files:
docs/middleware/encryptcookie.mddocs/whats_new.md
📚 Learning: 2025-07-27T17:28:53.403Z
Learnt from: sixcolors
PR: gofiber/fiber#3625
File: middleware/session/config.go:57-58
Timestamp: 2025-07-27T17:28:53.403Z
Learning: In the session middleware `Config` struct, the `Extractor` field uses function closures (like `FromCookie(key)`), making it impossible to introspect extractor parameters at runtime for validation purposes without complex reflection techniques.
Applied to files:
docs/middleware/encryptcookie.mdmiddleware/keyauth/config.gomiddleware/keyauth/extractors.gomiddleware/keyauth/keyauth.godocs/middleware/keyauth.mdmiddleware/keyauth/keyauth_test.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: dave-gray101
PR: gofiber/fiber#3027
File: docs/api/middleware/keyauth.md:222-222
Timestamp: 2024-10-08T19:06:06.583Z
Learning: The `KeyLookup` field in the `keyauth` middleware does not support pipe-delimited keys. Instead, it specifies a single source and name for key extraction, with additional sources specified in the `FallbackKeyLookups` field.
Applied to files:
middleware/keyauth/keyauth.godocs/whats_new.mddocs/middleware/keyauth.md
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
PR: gofiber/fiber#2922
File: middleware/cors/utils.go:63-71
Timestamp: 2024-10-08T19:06:06.583Z
Learning: The project uses the testify/assert package for assertions in unit tests.
Applied to files:
middleware/keyauth/keyauth_test.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/session/store.go:164-167
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Unit tests in this project use testify require.
Applied to files:
middleware/keyauth/keyauth_test.go
📚 Learning: 2024-12-13T08:14:22.851Z
Learnt from: efectn
PR: gofiber/fiber#3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.
Applied to files:
middleware/keyauth/keyauth_test.go
🧬 Code Graph Analysis (5)
middleware/keyauth/config_test.go (3)
middleware/keyauth/keyauth.go (1)
New(23-55)middleware/keyauth/config.go (2)
Config(10-42)ConfigDefault(45-64)middleware/keyauth/extractors.go (2)
Extractor(42-48)FromHeader(164-176)
middleware/keyauth/extractors_test.go (1)
middleware/keyauth/extractors.go (16)
FromParam(118-130)ErrMissingAPIKeyInParam(54-54)FromForm(141-153)ErrMissingAPIKeyInForm(55-55)FromQuery(187-199)ErrMissingAPIKeyInQuery(53-53)FromHeader(164-176)ErrMissingAPIKeyInHeader(52-52)FromAuthHeader(61-84)FromCookie(95-107)ErrMissingAPIKeyInCookie(56-56)Chain(211-252)ErrMissingAPIKey(51-51)Extractor(42-48)Source(12-12)SourceCustom(38-38)
middleware/keyauth/config.go (3)
middleware/keyauth/extractors.go (8)
Extractor(42-48)ErrMissingAPIKey(51-51)ErrMissingAPIKeyInHeader(52-52)ErrMissingAPIKeyInQuery(53-53)ErrMissingAPIKeyInParam(54-54)ErrMissingAPIKeyInForm(55-55)ErrMissingAPIKeyInCookie(56-56)FromAuthHeader(61-84)middleware/keyauth/keyauth.go (1)
ErrMissingOrMalformedAPIKey(20-20)constants.go (2)
StatusUnauthorized(74-74)HeaderAuthorization(163-163)
middleware/keyauth/extractors.go (2)
ctx_interface_gen.go (1)
Ctx(17-405)middleware/keyauth/keyauth.go (1)
New(23-55)
middleware/keyauth/keyauth.go (2)
middleware/keyauth/extractors.go (4)
Extractor(42-48)Source(12-12)SourceAuthHeader(20-20)Chain(211-252)constants.go (1)
HeaderWWWAuthenticate(166-166)
🪛 LanguageTool
docs/middleware/keyauth.md
[grammar] ~263-~263: There might be a mistake here.
Context: ... | nil (panic) | | Extractor | keyauth.Extractor ...
(QB_NEW_EN)
🪛 Gitleaks (8.27.2)
docs/middleware/keyauth.md
216-220: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Compare
- GitHub Check: repeated
🔇 Additional comments (18)
docs/middleware/encryptcookie.md (1)
98-98: CSRF extractor usage change looks correctReplacing KeyLookup with
Extractor: csrf.FromHeader(csrf.HeaderName)aligns with the v3 extractor pattern and the broader middleware refactor.middleware/keyauth/config_test.go (1)
30-43: Good coverage of defaulting behaviorAsserts for default Realm and non-nil handlers/extractor cover the happy path when only Validator is provided.
docs/whats_new.md (1)
1115-1117: KeyAuth: Extractor-based configuration note is accurateClear summary of breaking changes and the new Extractor/Chain model. Nicely done.
middleware/keyauth/extractors_test.go (3)
83-90: Context acquisition and deferred release pattern looks correct.The pattern of acquiring the context, deferring its release, and then performing the test is appropriate and follows good resource management practices.
18-27: Well-structured test for missing parameter extraction.The test correctly validates that
FromParamreturnsErrMissingAPIKeyInParamwhen the parameter is missing from the route. The pattern of creating a test route and validating the error within the handler is appropriate.
124-175: Comprehensive chain testing with good edge case coverage.The test thoroughly covers the chain functionality including:
- Empty chain returning
ErrMissingAPIKey- First extractor succeeding
- Second extractor succeeding when first fails
- All extractors failing returns the last error
- Custom extractor returning empty string and nil error
This provides excellent coverage of the chain behavior.
docs/middleware/keyauth.md (3)
17-61: Excellent example demonstrating cookie-based authentication.The basic example clearly shows how to use the new
ExtractorAPI with cookie-based authentication, including proper key validation using constant-time comparison.
224-254: Clear documentation of the new Extractor API and chaining capability.The documentation effectively explains:
- The available built-in extractors with clear descriptions
- How to use the
Chainfunction to combine multiple extractors- A practical example showing header and query parameter fallback
This provides excellent guidance for users migrating to the new API.
274-289: Well-structured default configuration with comprehensive error handling.The default configuration appropriately:
- Maps all specific missing API key errors to proper status codes
- Provides clear error messages for each scenario
- Sets a default realm for WWW-Authenticate headers
- Uses the common Bearer authentication scheme by default
middleware/keyauth/keyauth.go (4)
15-17: Good use of iota for context key enumeration.Using
iotafor the context key is a cleaner approach than hardcoding values, and it follows Go best practices for constant enumeration.
27-29: Appropriate extraction of auth scheme at initialization.Determining the auth scheme once during middleware initialization is efficient and avoids repeated computation on each request.
38-46: Clean refactor to use the new Extractor API.The extraction and validation flow is well-structured:
- Extract the key using the configured extractor
- Validate only if extraction succeeds
- Store the key in context and call success handler on valid key
This is a significant improvement over the previous string-based configuration approach.
48-52: Proper WWW-Authenticate header handling.Setting the WWW-Authenticate header with the auth scheme and realm when authentication fails follows RFC 7235 requirements for HTTP authentication.
middleware/keyauth/keyauth_test.go (5)
24-31: Good extraction of test constants for clarity.Defining extractor names as constants improves test readability and maintainability.
76-86: Appropriate handling of special characters in test keys.The test correctly handles encoding issues by using simpler keys for param and cookie extractors where special characters could cause problems in the test setup. This is a pragmatic approach for testing.
192-195: Verify the condition logic for empty test key.The condition checks
testKeywhich may have been modified from the originaltest.APIKey. Consider whether you want to check the original API key value or the potentially modified one.Based on the test logic, when
authSource == paramExtractorNameand the originaltest.APIKeyis empty,testKeyremains empty (lines 76-86 only modify non-empty keys). So checkingtestKeyhere appears correct, but adding a comment would improve clarity.+ // When using param extractor with empty key, the route won't match if authSource == paramExtractorName && testKey == "" { expectedCode = 404 expectedBody = "Cannot GET /" }
800-897: Comprehensive WWW-Authenticate header testing.Excellent test coverage for the WWW-Authenticate header behavior including:
- Default configuration
- Custom realm
- Non-auth-header extractors (no header expected)
- Success cases (no header expected)
- Chained extractors with and without auth headers
The parallel test execution and table-driven approach is well-structured.
472-475: Good addition of protected endpoint test.Adding the
/not-allowedendpoint improves test coverage by verifying that the middleware correctly protects routes when theNextfunction returns false.
| The keyauth middleware was updated to introduce a configurable `Realm` field for the `WWW-Authenticate` header. | ||
| The old string-based `KeyLookup` configuration has been replaced with an `Extractor` field. Use helper functions like `keyauth.FromHeader`, `keyauth.FromAuthHeader`, or `keyauth.FromCookie` to define where the key should be retrieved from. Multiple sources can be combined with `keyauth.Chain`. See the migration guide below. | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Ensure WWW-Authenticate behavior is documented for chained extractors
When an auth scheme is present (e.g., FromAuthHeader), the middleware sets the WWW-Authenticate header. If the first extractor in the chain doesn’t specify a scheme, this header may be omitted. Mention that for chains, the scheme is inferred from the first extractor.
To verify code behavior, search for getAuthScheme and confirm it inspects Extractor.AuthScheme and/or walks Extractor.Chain:
Also applies to: 1943-1963
🏁 Script executed:
#!/bin/bash
ast-grep --pattern $'func $_($_$) $_ {\n $$$\n getAuthScheme($_)\n $$$\n}' -A 3
rg -n "getAuthScheme|WWW-Authenticate|HeaderWWWAuthenticate" -A 5Length of output: 9722
Document WWW-Authenticate behavior for chained extractors
Short summary: I verified getAuthScheme inspects Extractor.AuthScheme and walks Extractor.Chain: it returns the first auth scheme from an extractor using the Authorization header and, if none is found, the middleware does not set the WWW-Authenticate header. Please document this behavior.
Files/locations to update:
- middleware/keyauth/keyauth.go — getAuthScheme implementation (around lines 67–74) shows it checks e.Source == SourceAuthHeader and iterates e.Chain to find a matching AuthScheme.
- docs/whats_new.md — update both occurrences (around lines 1115–1117 and 1944–1946) to mention chained extractor behavior.
- docs/middleware/keyauth.md — add a short note in the Extractor/Realm/default-config section explaining when WWW-Authenticate is set for chains.
- (optional) middleware/keyauth/keyauth_test.go — referenced tests (e.g., ~lines 682/702) confirm current behavior; update tests or add examples in docs if helpful.
Suggested wording to add:
"Note: When using keyauth.Chain the WWW-Authenticate header is inferred from the first extractor that uses the Authorization header (i.e., the first extractor with SourceAuthHeader). If no extractor in the chain provides an auth scheme, the WWW-Authenticate header will not be set."
| ErrorHandler: func(c fiber.Ctx, err error) error { | ||
| switch { | ||
| case errors.Is(err, ErrMissingOrMalformedAPIKey), | ||
| errors.Is(err, ErrMissingAPIKey), | ||
| errors.Is(err, ErrMissingAPIKeyInHeader), | ||
| errors.Is(err, ErrMissingAPIKeyInQuery), | ||
| errors.Is(err, ErrMissingAPIKeyInParam), | ||
| errors.Is(err, ErrMissingAPIKeyInForm), | ||
| errors.Is(err, ErrMissingAPIKeyInCookie): | ||
| return c.Status(fiber.StatusUnauthorized).SendString(err.Error()) | ||
| } | ||
| return c.Status(fiber.StatusUnauthorized).SendString("Invalid or expired API Key") | ||
| }, | ||
| Realm: "Restricted", | ||
| Extractor: FromAuthHeader(fiber.HeaderAuthorization, "Bearer"), | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Default ErrorHandler mapping looks good; confirm it aligns with extractor errors
The mapping for missing/malformed errors to 401 with specific messages is appropriate and consistent with the extractor set. After adopting the suggested ErrMissingOrMalformedAPIKey returns in FromAuthHeader, this will produce better client feedback.
To ensure all extractor errors are covered by the default handler, grep for error declarations and references:
Also applies to: 79-91
🏁 Script executed:
#!/bin/bash
rg -n "var \(|ErrMissing.*\)" middleware/keyauth -A 2
rg -n "ErrorHandler.*switch" -n middleware/keyauth/config.go -A 40Length of output: 7772
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- Files with FromAuthHeader ----"
rg -n "FromAuthHeader" middleware/keyauth || true
echo
echo "---- Show middleware/keyauth/extractors.go (first 240 lines) ----"
sed -n '1,240p' middleware/keyauth/extractors.go || true
echo
echo "---- Show middleware/keyauth/config.go (1-160) ----"
sed -n '1,160p' middleware/keyauth/config.go || trueLength of output: 12286
Default ErrorHandler OK — return ErrMissingOrMalformedAPIKey for malformed Authorization header
Confirmed: ConfigDefault.ErrorHandler already covers ErrMissingOrMalformedAPIKey and all ErrMissingAPIKey* variants. However, FromAuthHeader currently returns ErrMissingAPIKeyInHeader for both a missing header and a header with the wrong scheme. I recommend returning ErrMissingOrMalformedAPIKey when the Authorization header is present but malformed so clients receive clearer feedback.
Files to update / check:
- middleware/keyauth/config.go — ErrorHandler switch already includes ErrMissingOrMalformedAPIKey.
- middleware/keyauth/extractors.go — FromAuthHeader: change the branch that handles a wrong/mismatched scheme to return ErrMissingOrMalformedAPIKey instead of ErrMissingAPIKeyInHeader.
- middleware/keyauth/keyauth.go — ErrMissingOrMalformedAPIKey is defined here.
- Tests to update: middleware/keyauth/extractors_test.go and middleware/keyauth/keyauth_test.go (they assert ErrMissingAPIKeyInHeader in scheme-mismatch cases).
🤖 Prompt for AI Agents
In middleware/keyauth/extractors.go around the branch that handles Authorization
header scheme mismatches, change the returned error from
ErrMissingAPIKeyInHeader to ErrMissingOrMalformedAPIKey so presence of a header
with the wrong scheme is treated as malformed; keep middleware/keyauth/config.go
as-is since ErrorHandler already handles ErrMissingOrMalformedAPIKey; update
tests in middleware/keyauth/extractors_test.go and
middleware/keyauth/keyauth_test.go to expect ErrMissingOrMalformedAPIKey for the
scheme-mismatch cases and adjust any assertions/messages accordingly.
| // configDefault is a helper function to set default values | ||
| func configDefault(config ...Config) Config { | ||
| // Return default config if nothing provided | ||
| if len(config) < 1 { | ||
| return ConfigDefault | ||
| panic("fiber: keyauth middleware requires a validator function") | ||
| } | ||
|
|
||
| // Override default config | ||
| cfg := config[0] | ||
|
|
||
| // Require a validator function | ||
| if cfg.Validator == nil { | ||
| panic("fiber: keyauth middleware requires a validator function") | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid double-panic paths; handle empty config gracefully then validate
You panic for both “no config provided” and “validator is nil”, with the same message. Handle the no-config case by initializing cfg to zero value, then enforce the validator requirement. This reduces special cases and simplifies reasoning.
-func configDefault(config ...Config) Config {
- // Return default config if nothing provided
- if len(config) < 1 {
- panic("fiber: keyauth middleware requires a validator function")
- }
- cfg := config[0]
+func configDefault(config ...Config) Config {
+ // Initialize cfg from provided value or zero value
+ var cfg Config
+ if len(config) > 0 {
+ cfg = config[0]
+ }
// Require a validator function
if cfg.Validator == nil {
panic("fiber: keyauth middleware requires a validator function")
}This preserves the panic message for New() without arguments while streamlining the code.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // configDefault is a helper function to set default values | |
| func configDefault(config ...Config) Config { | |
| // Return default config if nothing provided | |
| if len(config) < 1 { | |
| return ConfigDefault | |
| panic("fiber: keyauth middleware requires a validator function") | |
| } | |
| // Override default config | |
| cfg := config[0] | |
| // Require a validator function | |
| if cfg.Validator == nil { | |
| panic("fiber: keyauth middleware requires a validator function") | |
| } | |
| // configDefault is a helper function to set default values | |
| func configDefault(config ...Config) Config { | |
| // Initialize cfg from provided value or zero value | |
| var cfg Config | |
| if len(config) > 0 { | |
| cfg = config[0] | |
| } | |
| // Require a validator function | |
| if cfg.Validator == nil { | |
| panic("fiber: keyauth middleware requires a validator function") | |
| } |
🤖 Prompt for AI Agents
In middleware/keyauth/config.go around lines 66 to 78, avoid the double-panic by
treating an absent config as the zero value and then validating the Validator
field; change the flow so if len(config) == 0 you set cfg to Config{} instead of
panicking, and after that check if cfg.Validator == nil and panic with the
existing message if so — this preserves the panic for New() without a validator
while removing the redundant early panic for missing config.
| // Check if the header starts with the specified auth scheme | ||
| if authScheme != "" { | ||
| schemeLen := len(authScheme) | ||
| if len(authHeader) > schemeLen+1 && strings.EqualFold(authHeader[:schemeLen], authScheme) && authHeader[schemeLen] == ' ' { | ||
| return strings.TrimSpace(authHeader[schemeLen+1:]), nil | ||
| } | ||
| return "", ErrMissingAPIKeyInHeader | ||
| } | ||
|
|
||
| return strings.TrimSpace(authHeader), nil | ||
| }, |
There was a problem hiding this comment.
Authorization parsing can return empty token; treat whitespace-only or malformed values as errors
If the header is "Bearer " (or has extra whitespace), strings.TrimSpace(authHeader[schemeLen+1:]) can yield an empty token while returning nil error. This leads to calling Validator with an empty key. Also, when the scheme doesn't match, returning ErrMissingAPIKeyInHeader hides malformed inputs. Prefer ErrMissingOrMalformedAPIKey and ensure the token is non-empty.
Apply this diff to harden parsing and avoid empty-token success:
- // Check if the header starts with the specified auth scheme
- if authScheme != "" {
- schemeLen := len(authScheme)
- if len(authHeader) > schemeLen+1 && strings.EqualFold(authHeader[:schemeLen], authScheme) && authHeader[schemeLen] == ' ' {
- return strings.TrimSpace(authHeader[schemeLen+1:]), nil
- }
- return "", ErrMissingAPIKeyInHeader
- }
-
- return strings.TrimSpace(authHeader), nil
+ // Check if the header starts with the specified auth scheme
+ if authScheme != "" {
+ schemeLen := len(authScheme)
+ if len(authHeader) < schemeLen+1 || !strings.EqualFold(authHeader[:schemeLen], authScheme) {
+ return "", ErrMissingOrMalformedAPIKey
+ }
+ // Trim any whitespace after the scheme and ensure a non-empty token
+ token := strings.TrimSpace(authHeader[schemeLen:])
+ if token == "" {
+ return "", ErrMissingOrMalformedAPIKey
+ }
+ return token, nil
+ }
+
+ token := strings.TrimSpace(authHeader)
+ if token == "" {
+ return "", ErrMissingAPIKeyInHeader
+ }
+ return token, nil📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Check if the header starts with the specified auth scheme | |
| if authScheme != "" { | |
| schemeLen := len(authScheme) | |
| if len(authHeader) > schemeLen+1 && strings.EqualFold(authHeader[:schemeLen], authScheme) && authHeader[schemeLen] == ' ' { | |
| return strings.TrimSpace(authHeader[schemeLen+1:]), nil | |
| } | |
| return "", ErrMissingAPIKeyInHeader | |
| } | |
| return strings.TrimSpace(authHeader), nil | |
| }, | |
| // Check if the header starts with the specified auth scheme | |
| if authScheme != "" { | |
| schemeLen := len(authScheme) | |
| if len(authHeader) < schemeLen+1 || !strings.EqualFold(authHeader[:schemeLen], authScheme) { | |
| return "", ErrMissingOrMalformedAPIKey | |
| } | |
| // Trim any whitespace after the scheme and ensure a non-empty token | |
| token := strings.TrimSpace(authHeader[schemeLen:]) | |
| if token == "" { | |
| return "", ErrMissingOrMalformedAPIKey | |
| } | |
| return token, nil | |
| } | |
| token := strings.TrimSpace(authHeader) | |
| if token == "" { | |
| return "", ErrMissingAPIKeyInHeader | |
| } | |
| return token, nil | |
| }, |
🤖 Prompt for AI Agents
In middleware/keyauth/extractors.go around lines 69 to 79, the current
auth-header parsing can return an empty token (e.g., "Bearer ") or return
ErrMissingAPIKeyInHeader for malformed inputs; update the logic to TrimSpace the
extracted token and if the result is empty return ErrMissingOrMalformedAPIKey,
and when the scheme check fails return ErrMissingOrMalformedAPIKey instead of
ErrMissingAPIKeyInHeader; also apply the same non-empty trimmed check for the
branch that returns the whole header so no whitespace-only token ever returns
nil error.
|
cli pr gofiber/cli#157 |
Description
This PR modernizes the
keyauthmiddleware by replacing the legacy string-based key extraction configuration (KeyLookup,AuthScheme,CustomKeyLookup) with a robust, type-safe extractor system. This update improves flexibility, composability, and clarity for users implementing API key authentication in Fiber.Key Changes
Extractor-Based API Key Retrieval:
Extractorfield for configuration, allowing explicit selection of where to extract the API key (header, auth header, query, param, cookie, form).FromHeader(header string)FromAuthHeader(header, scheme string)FromCookie(name string)FromQuery(param string)FromParam(param string)FromForm(name string)Chain(...)for chaining multiple extractors.Config Refactoring:
KeyLookup,AuthScheme, andCustomKeyLookup.Validatorrequired; panics if missing.WWW-Authenticate Header and Realm:
WWW-Authenticateheader on authentication failure when using an auth header extractor.Realmsupport.Comprehensive Tests and Documentation:
Migration Guide
Before:
After:
You can combine multiple sources:
Why?
Explicit extractor configuration avoids fragile string parsing and makes it easier to extend/customize key source logic.
Easily combine multiple sources for API key retrieval using
Chain.Consistent error messages and
WWW-Authenticatesupport.No ambiguous string formats; everything is explicit and documented.
Breaking Changes:
KeyLookup,AuthScheme, andCustomKeyLookuphave been removed.Extractorconfiguration with provided helper functions.See updated documentation and migration notes for further details.