Skip to content

Proposal fix for #4280 with no breaking changes #4289

Closed
joyride9999 wants to merge 1 commit into
gofiber:mainfrom
joyride9999:main
Closed

Proposal fix for #4280 with no breaking changes #4289
joyride9999 wants to merge 1 commit into
gofiber:mainfrom
joyride9999:main

Conversation

@joyride9999
Copy link
Copy Markdown

Proposal fix for #4280 with no breaking changes

Added possibility to dynamically control the cookie name and tokenKey using context variable. Rationale is that sometimes csrf token is overridden .. e.g. using SingleUseToken true , and having more then 1 browser windows/tab opened it may happen that all browser sessions use the same csrf token, but on the first post on any window the csrf will be accepted and changed, the other windows will have to be hard refreshed as their token got invalidated. This fix allows control of csrf token on a per window/tab session

Description

Added possibility to dynamically control the cookie name and tokenKey using context variable. Rationale is that sometimes csrf token is overridden ..

Fixes # (issue)
e.g. using SingleUseToken true , and having more then 1 browser windows/tab opened it may happen that all browser sessions use the same csrf token, but on the first post on any window the csrf will be accepted and changed, the other windows will have to be hard refreshed as their token got invalidated. This fix allows control of csrf token on a per window/tab session

Changes introduced

Added CookieNameFn .. if this is used then default CookieName and tokenKey will use this function instead of the default. If not used then CookieName will behave similar.

  • Examples: Provide examples demonstrating the new features or changes in action.
    Setting up configuration :
    `
    csrfConfig := csrf.Config{
    Session: store,
    Extractor: extractors.FromHeader("X-Csrf-Token"), // In this example, we will be using a hidden input field to store the CSRF token
    CookieNameFN: GetCSRFByTabId, // set our function
    .................
    SingleUseToken: true,
    }
    return csrfConfig

// Example function that gets the tab id sent from the frontend)
func GetCSRFByTabId(ctx fiber.Ctx) string {

if ctx == nil {
	return "_csrf"
}

//gets it from header
tabid := ctx.Get("X-Tab-ID")
if tabid == "" {
	return "_csrf"
}
return tabid

}

`

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

Checklist

Before you submit your pull request, please make sure you meet these requirements:

  • [x ] Followed the inspiration of the Express.js framework for new functionalities, making them similar in usage.
  • [ x] Conducted a self-review of the code and provided comments for complex or critical parts.
  • [ x] Updated the documentation in the /docs/ directory for Fiber's documentation.
  • [ x] Added or updated unit tests to validate the effectiveness of the changes or new features.
  • [ x] Ensured that new and existing unit tests pass locally with the changes.
  • [ x] Verified that any new dependencies are essential and have been agreed upon by the maintainers/community.
  • [ x] Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

Commit formatting

Please use emojis in commit messages for an easy way to identify the purpose or intention of a commit. Check out the emoji cheatsheet here: CONTRIBUTING.md

…ests are passing,

Added possibility to dynamically control the cookie name and tokenKey using context variable.
Rationale is that sometimes csrf token is overridden .. e.g. using SingleUseToken true , and having more then 1 browser windows/tab opened it may happen that all browser sessions use the same csrf token, but on the first post on any window the csrf will be accepted and changed, the other windows will have to be hard refreshed as their token got invalidated.
This fix allows control of csrf token on a per window/tab session
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

Review Change Stack

Walkthrough

The CSRF middleware adds per-request dynamic cookie naming. A new CookieNameFn field in the configuration allows deriving the CSRF cookie name from the request context. This name is now consistently computed and used throughout cookie reading, token validation, context storage, cookie setting, and token deletion.

Changes

CSRF Dynamic Cookie Name Support

Layer / File(s) Summary
Config contract and validation
middleware/csrf/config.go
csrf.Config gains CookieNameFn function field to dynamically derive the CSRF cookie name from fiber.Ctx. Validation logic in validateExtractorSecurity now computes the cookie name from this function (when present) and enforces security checks for both primary and chained extractors against the derived name.
Request handler and token storage
middleware/csrf/csrf.go
The middleware handler computes the per-request cookie name using CookieNameFn when present, reads CSRF cookies by the computed name, validates extracted tokens against the computed cookie value, and stores tokens in Fiber context under a dynamic key (computed cookieName when CookieNameFn is set, otherwise fixed tokenKey).
Token retrieval and cookie management
middleware/csrf/csrf.go
TokenFromContext signature updates to accept optional variadic cookieKey parameter for context lookup; setCSRFCookie and Handler.DeleteToken both now compute the cookie name dynamically before setting or clearing the CSRF cookie.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • gofiber/fiber#3706: Both PRs touch the CSRF context access API around csrf.TokenFromContext—the retrieved PR documents the move to context helpers, while this PR changes TokenFromContext to support retrieving tokens via a dynamically derived cookie name/key.
  • gofiber/fiber#3630: This PR extends the existing CSRF extractor security validation logic to compare against a per-request cookie name derived from CookieNameFn, building directly on the retrieved PR's new Extractor metadata and cookie-based insecure-extractor validation.

Suggested labels

✏️ Feature, 📒 Documentation

Suggested reviewers

  • gaby
  • efectn
  • ReneWerner87

Poem

🐰 A cookie name that dances free,
Per-request, oh how it ought to be!
Dynamic deriving, context-aware,
CSRF tokens floating through the air—
Safe, secure, and wonderfully rare! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly references the issue (#4280) and clearly indicates a fix with no breaking changes, accurately summarizing the main intent of the changeset.
Description check ✅ Passed The description addresses the core problem, provides configuration examples with the new CookieNameFn feature, and covers most required sections; however, benchmarks section is incomplete and documentation/migration details are mentioned but not substantively detailed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ReneWerner87 ReneWerner87 added this to v3 May 17, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone May 17, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a CookieNameFn field to the CSRF middleware configuration, enabling dynamic cookie names. The review feedback identifies a potential panic risk when invoking this function with a nil context during configuration validation and suggests refactoring variable initializations into explicit if/else blocks. Furthermore, the reviewer advises against using dynamic keys for context storage, as it breaks the TokenFromContext helper and logging; they recommend maintaining the use of the internal tokenKey and reverting the breaking change to the TokenFromContext signature to ensure backward compatibility.

Comment thread middleware/csrf/config.go
Comment on lines +191 to +194
cookieName := cfg.CookieName
if cfg.CookieNameFn != nil {
cookieName = cfg.CookieNameFn(nil)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Following the general rules, variable initializations should be refactored into explicit if/else blocks to avoid overwriting. Additionally, calling CookieNameFn(nil) poses a risk of panicking if the user-provided function does not explicitly handle a nil context. While the example in the PR description handles it, this should be documented or handled more defensively to avoid crashes during middleware initialization.

	var cookieName string
	if cfg.CookieNameFn != nil {
		// Note: CookieNameFn is called with nil context during config validation.
		// Users must handle nil context in their implementation to avoid panics at startup.
		cookieName = cfg.CookieNameFn(nil)
	} else {
		cookieName = cfg.CookieName
	}
References
  1. Prefer refactoring variable initializations into explicit if/else blocks to avoid using linter suppressions like wastedassign when a variable is conditionally assigned or immediately overwritten.

Comment thread middleware/csrf/csrf.go
Comment on lines +227 to +235
var t any
if cfg.CookieNameFn == nil {
t = tokenKey
} else {
t = cookieName
}

// Store the token in the context
fiber.StoreInContext(c, tokenKey, token)
fiber.StoreInContext(c, t, token)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Storing the CSRF token in the context using a dynamic string key (cookieName) instead of the internal tokenKey breaks the standard TokenFromContext(c) helper and the built-in logger tag (csrf-token). Since the request context is already scoped to a single request, there is no need for a dynamic key here. Always using tokenKey ensures compatibility with existing utilities and logging while still supporting dynamic cookie names on the transport layer.

		// Store the token in the context
		fiber.StoreInContext(c, tokenKey, token)

Comment thread middleware/csrf/csrf.go
Comment on lines +254 to +267
func TokenFromContext(ctx any, cookieKey ...string) string {

// variadic function in order not to introduce breaking changes, if cookie name is not sent it uses tokenKey as default
var tK any
if len(cookieKey) == 1 {
tK = cookieKey[0]
if tK == "" {
tK = tokenKey
}
} else {
tK = tokenKey
}

if token, ok := fiber.ValueFromContext[string](ctx, tK); ok {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Changing the signature of TokenFromContext to be variadic is a breaking change for users who use this function as a value (e.g., passing it to another function). Furthermore, if the token is always stored under tokenKey in the middleware (as suggested), this complexity and the extra allocations from the variadic slice are unnecessary. Reverting this change maintains backward compatibility as promised in the PR title.

func TokenFromContext(ctx any) string {
	if token, ok := fiber.ValueFromContext[string](ctx, tokenKey); ok {
		return token
	}
	return ""
}

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@middleware/csrf/config.go`:
- Around line 191-206: The startup validation calls CookieNameFn with nil which
can panic and misses dynamic values; change the logic so that if
cfg.CookieNameFn != nil you skip the nil-call startup check (do not compute
cookieName from CookieNameFn at construction) and instead perform the
isInsecureCookieExtractor checks at request time after computing cookieName from
the real fiber.Ctx (i.e., in the middleware handler where cookieName is
resolved), and if CookieNameFn is nil keep the existing startup checks using
cfg.CookieName; ensure you still validate both the primary extractor via
isInsecureCookieExtractor(cfg.Extractor, cookieName) and each chained extractor
in cfg.Extractor.Chain at request time when CookieNameFn is used.

In `@middleware/csrf/csrf.go`:
- Around line 227-235: Always store the CSRF token under the canonical tokenKey
so TokenFromContext(c) and the "csrf-token" log tag continue to work: call
fiber.StoreInContext(c, tokenKey, token) unconditionally; if cfg.CookieNameFn is
non-nil also mirror the token under cookieName by calling
fiber.StoreInContext(c, cookieName, token) so keyed lookups that rely on the
cookie name still work; adjust the existing branch that currently stores only
under cookieName (inspect cfg.CookieNameFn, tokenKey, cookieName, token and
fiber.StoreInContext) to implement this unconditional + optional dual-store
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d49131a5-582a-4afa-af27-bfb73daca773

📥 Commits

Reviewing files that changed from the base of the PR and between 7e4493e and 3304f02.

📒 Files selected for processing (2)
  • middleware/csrf/config.go
  • middleware/csrf/csrf.go

Comment thread middleware/csrf/config.go
Comment on lines +191 to +206
cookieName := cfg.CookieName
if cfg.CookieNameFn != nil {
cookieName = cfg.CookieNameFn(nil)
}

// Check primary extractor
if isInsecureCookieExtractor(cfg.Extractor, cfg.CookieName) {
panic("CSRF: Extractor reads from the same cookie '" + cfg.CookieName +
if isInsecureCookieExtractor(cfg.Extractor, cookieName) {
panic("CSRF: Extractor reads from the same cookie '" + cookieName +
"' used for token storage. This completely defeats CSRF protection.")
}

// Check chained extractors
for i, extractor := range cfg.Extractor.Chain {
if isInsecureCookieExtractor(extractor, cfg.CookieName) {
if isInsecureCookieExtractor(extractor, cookieName) {
panic(fmt.Sprintf("CSRF: Chained extractor #%d reads from the same cookie '%s' "+
"used for token storage. This completely defeats CSRF protection.", i+1, cfg.CookieName))
"used for token storage. This completely defeats CSRF protection.", i+1, cookieName))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Don't invoke CookieNameFn with a nil context here.

This runs during middleware construction, so any normal CookieNameFn that reads from c will panic before the app starts. It also only validates one synthetic name, so a dynamic function can still evade the insecure-cookie extractor check for other request-time values. Please move this validation to the request path after cookieName is computed from a real fiber.Ctx, or skip startup validation when CookieNameFn is configured and enforce it later instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@middleware/csrf/config.go` around lines 191 - 206, The startup validation
calls CookieNameFn with nil which can panic and misses dynamic values; change
the logic so that if cfg.CookieNameFn != nil you skip the nil-call startup check
(do not compute cookieName from CookieNameFn at construction) and instead
perform the isInsecureCookieExtractor checks at request time after computing
cookieName from the real fiber.Ctx (i.e., in the middleware handler where
cookieName is resolved), and if CookieNameFn is nil keep the existing startup
checks using cfg.CookieName; ensure you still validate both the primary
extractor via isInsecureCookieExtractor(cfg.Extractor, cookieName) and each
chained extractor in cfg.Extractor.Chain at request time when CookieNameFn is
used.

Comment thread middleware/csrf/csrf.go
Comment on lines +227 to +235
var t any
if cfg.CookieNameFn == nil {
t = tokenKey
} else {
t = cookieName
}

// Store the token in the context
fiber.StoreInContext(c, tokenKey, token)
fiber.StoreInContext(c, t, token)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep storing the token under tokenKey as well.

When CookieNameFn is enabled, TokenFromContext(c) still defaults to tokenKey, so existing handlers/templates and the built-in "csrf-token" log tag start returning "". Store the token under tokenKey unconditionally, and optionally mirror it under cookieName if keyed lookup is still needed.

Suggested fix
-		var t any
-		if cfg.CookieNameFn == nil {
-			t = tokenKey
-		} else {
-			t = cookieName
-		}
-
-		// Store the token in the context
-		fiber.StoreInContext(c, t, token)
+		// Preserve existing lookups.
+		fiber.StoreInContext(c, tokenKey, token)
+		if cfg.CookieNameFn != nil {
+			fiber.StoreInContext(c, cookieName, token)
+		}
📝 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.

Suggested change
var t any
if cfg.CookieNameFn == nil {
t = tokenKey
} else {
t = cookieName
}
// Store the token in the context
fiber.StoreInContext(c, tokenKey, token)
fiber.StoreInContext(c, t, token)
// Preserve existing lookups.
fiber.StoreInContext(c, tokenKey, token)
if cfg.CookieNameFn != nil {
fiber.StoreInContext(c, cookieName, token)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@middleware/csrf/csrf.go` around lines 227 - 235, Always store the CSRF token
under the canonical tokenKey so TokenFromContext(c) and the "csrf-token" log tag
continue to work: call fiber.StoreInContext(c, tokenKey, token) unconditionally;
if cfg.CookieNameFn is non-nil also mirror the token under cookieName by calling
fiber.StoreInContext(c, cookieName, token) so keyed lookups that rely on the
cookie name still work; adjust the existing branch that currently stores only
under cookieName (inspect cfg.CookieNameFn, tokenKey, cookieName, token and
fiber.StoreInContext) to implement this unconditional + optional dual-store
behavior.

@gaby
Copy link
Copy Markdown
Member

gaby commented May 17, 2026

This is a breaking change in two locations and is unnecessary.

@sixcolors
Copy link
Copy Markdown
Member

This PR shouldn't be merged. It takes a working, collision-safe design and actively breaks it.

The core problem

Your original issue was predicated on tokenKey colliding between packages. That was incorrect, each package's contextKey type is distinct, so csrf.tokenKey and jwt.tokenKey can never overwrite each other.

This patch replaces the safe, typed key with a string key in the critical path:

var t any
if cfg.CookieNameFn == nil {
    t = tokenKey
} else {
    t = cookieName  // string key — now collisions are possible
}

When CookieNameFn is set, StoreInContext(c, cookieName, token) stores the token under a plain string. If two handlers or middlewares use the same dynamic cookie name, they will overwrite each other. You have introduced the exact bug the issue incorrectly claimed existed.

API concerns

TokenFromContext(ctx any, cookieKey ...string) is a poor API. Variadic string arguments are not discoverable, not type-safe, and create confusion about whether the key is a typed constant or an arbitrary string.

Summary

  • The original collision claim was wrong.
  • This PR does not fix a bug; it creates one by abandoning typed keys for string keys.
  • Dynamic cookie names for CSRF tokens are not a supported pattern in Fiber and are not needed for tab/window isolation.

Closing this. If you have a concrete use case for dynamic cookie names that cannot be solved by existing configuration, please open a proposal with real-world requirements rather than a patch for a non-existent collision.

@sixcolors sixcolors closed this May 17, 2026
@github-project-automation github-project-automation Bot moved this to Done in v3 May 17, 2026
@joyride9999
Copy link
Copy Markdown
Author

This PR shouldn't be merged. It takes a working, collision-safe design and actively breaks it.

The core problem

Your original issue was predicated on tokenKey colliding between packages. That was incorrect, each package's contextKey type is distinct, so csrf.tokenKey and jwt.tokenKey can never overwrite each other.

This patch replaces the safe, typed key with a string key in the critical path:

var t any
if cfg.CookieNameFn == nil {
    t = tokenKey
} else {
    t = cookieName  // string key — now collisions are possible
}

When CookieNameFn is set, StoreInContext(c, cookieName, token) stores the token under a plain string. If two handlers or middlewares use the same dynamic cookie name, they will overwrite each other. You have introduced the exact bug the issue incorrectly claimed existed.

I think it is obvious that when the cookie name is handled through an external function then this becomes the caller's responsabilitty to avoid collisions

API concerns

TokenFromContext(ctx any, cookieKey ...string) is a poor API. Variadic string arguments are not discoverable, not type-safe, and create confusion about whether the key is a typed constant or an arbitrary string.

True, i mainly tried not to introduce breaking changes ..but i welcome other ideas if any.. i didn't put to much though in this :)

Summary

  • The original collision claim was wrong.
  • This PR does not fix a bug; it creates one by abandoning typed keys for string keys.
  • Dynamic cookie names for CSRF tokens are not a supported pattern in Fiber and are not needed for tab/window isolation.

Closing this. If you have a concrete use case for dynamic cookie names that cannot be solved by existing configuration, please open a proposal with real-world requirements rather than a patch for a non-existent collision.

I will open a new proposal then ( i commented in the bug with a real use case, where conflicts happen because the csrf is "static")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants