Skip to content

Add CsrfTokenCookieMiddleware - #100

Open
vjik wants to merge 13 commits into
masterfrom
token-middleware
Open

Add CsrfTokenCookieMiddleware#100
vjik wants to merge 13 commits into
masterfrom
token-middleware

Conversation

@vjik

@vjik vjik commented Aug 28, 2026

Copy link
Copy Markdown
Member
Q A
Is bugfix?
New feature? ✔️
Breaks BC?
Fix #99

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b5c9377-0f7e-4418-ab69-cf5c0a883865

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@vjik
vjik requested a balanced review from Copilot August 28, 2026 05:31
@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (7db5e85) to head (907ed93).

Additional details and impacted files
@@             Coverage Diff             @@
##              master      #100   +/-   ##
===========================================
  Coverage     100.00%   100.00%           
- Complexity        72        91   +19     
===========================================
  Files             10        11    +1     
  Lines            196       244   +48     
===========================================
+ Hits             196       244   +48     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds middleware that publishes CSRF tokens in response cookies for SPA cookie-to-header workflows.

Changes:

  • Adds configurable CSRF token cookie middleware.
  • Adds middleware tests and usage documentation.
  • Simplifies null-default handling.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/CsrfTokenCookieMiddleware.php Implements cookie publication.
tests/CsrfTokenCookieMiddlewareTest.php Tests cookie behavior.
README.md Documents configuration and usage.
CHANGELOG.md Records the feature.
src/StubCsrfToken.php Simplifies token initialization.
tests/ConfigTest.php Simplifies default parameter loading.
Suppressed comments (1)

README.md:626

  • This copyable configuration is invalid and registers the publisher twice: // or does not make the first entry alternative, and the second definition has no class value. It also places both publisher entries inside the validator, so rejected unsafe requests receive no token cookie. Keep one configured publisher before the validator and specify its class.
    CsrfTokenCookieMiddleware::class,
    // or
    [
        'class' => ,

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/CsrfTokenCookieMiddleware.php Outdated
Comment thread tests/CsrfTokenCookieMiddlewareTest.php Outdated
Comment thread src/CsrfTokenCookieMiddleware.php Outdated
Comment thread README.md Outdated
@vjik
vjik requested a balanced review from Copilot August 28, 2026 05:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

src/CsrfTokenCookieMiddleware.php:63

  • Issue #99 explicitly requires HttpOnly to be configurable, but this constructor has no such option and the header builder can never emit the attribute. Add a boolean option (defaulting to false for the cookie-to-header use case), append HttpOnly when enabled, and cover/document both settings.
    public function __construct(
        CsrfTokenInterface $token,
        string $cookieName = self::COOKIE_NAME,
        string $path = '/',
        ?string $domain = null,
        bool $secure = true,
        ?string $sameSite = self::SAME_SITE_LAX

Comment thread src/CsrfTokenCookieMiddleware.php
@vjik
vjik requested a balanced review from Copilot August 28, 2026 06:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/CsrfTokenCookieMiddleware.php:45

  • This whitelist contains literal spaces, so names such as X SRF pass even though whitespace is forbidden in cookie names. The $ anchor also permits a final LF, which can produce an invalid or injectable Set-Cookie header. Remove the formatting spaces and use an absolute end anchor.
    private const PATTERN_COOKIE_NAME = '/^[a-zA-Z0-9!#$%&\' * +\- .^_`|~]+$/';

src/CsrfTokenCookieMiddleware.php:78

  • Issue #99 explicitly requires a configurable HttpOnly option, but the constructor has no such parameter and the builder never emits HttpOnly. Add a boolean option (defaulting to false for the JavaScript-readable use case) and serialize, test, and document it so the requested API is complete.
        ?string $sameSite = self::SAME_SITE_LAX

@vjik
vjik requested a balanced review from Copilot August 28, 2026 06:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/CsrfTokenCookieMiddleware.php:98

  • This check only rejects control characters and semicolons; it accepts values such as an empty string, https://example.com, or not a domain. Those values are not valid RFC 6265 domain-values, so they are serialized into a Set-Cookie header that browsers reject instead of triggering the documented InvalidArgumentException. Validate the domain grammar (while accounting for supported host forms) and add invalid-domain cases.
        if ($domain !== null && preg_match(self::PATTERN_INVALID_ATTRIBUTE_VALUE, $domain)) {
            throw new InvalidArgumentException(
                sprintf('The cookie domain "%s" contains invalid characters.', $domain),
            );
        }

src/CsrfTokenCookieMiddleware.php:80

  • Issue #99 explicitly requires HttpOnly to be configurable, but the public constructor has no such option and the header builder can never emit the attribute. Add an httpOnly option (defaulting to false for SPA compatibility), serialize HttpOnly when enabled, and cover/document both settings.
        bool $secure = true,
        ?string $sameSite = self::SAME_SITE_LAX

@vjik
vjik requested a balanced review from Copilot August 28, 2026 06:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

src/CsrfTokenCookieMiddleware.php:31

  • The linked double-submit-cookie pattern validates a value derived from the request cookie, while this middleware explicitly never treats that cookie as proof and instead submits the token through a custom header. Pointing to the custom-request-header guidance avoids documenting this as a different CSRF pattern.
 * @link https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#alternative-using-a-double-submit-cookie-pattern

src/CsrfTokenCookieMiddleware.php:76

  • Issue #99 explicitly requires a configurable HttpOnly option, but this API always emits a JavaScript-readable cookie and provides no way to enable the attribute. Add a boolean option (defaulting to false), emit HttpOnly conditionally, and cover/document both settings.
        string $cookieName = self::COOKIE_NAME,
        string $path = '/',
        ?string $domain = null,
        bool $secure = true,
        ?string $sameSite = self::SAME_SITE_LAX

src/CsrfTokenCookieMiddleware.php:78

  • This validation still accepts an empty name and cookie separators such as = or spaces. For example, configuring foo=bar emits foo=bar=<token>, which creates a cookie named foo rather than foo=bar. Validate the name against the RFC cookie-name token grammar, not only header-injection characters.
        if (preg_match(self::PATTERN_HEADER_INJECTION, $cookieName)) {

@vjik vjik added the status:code review The pull request needs review. label Aug 28, 2026
@vjik
vjik requested a review from a team August 28, 2026 06:27
Comment thread src/CsrfTokenCookieMiddleware.php Outdated

$parts[] = 'Path=' . $this->path;

if ($this->secure) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be great if we could use autodetect in the secure option, so the user wouldn't have to configure anything.

$secure = $this->secure ?? strtolower($request->getUri()->getScheme()) === 'https';

This is how it works both in development with localhost and for production environments.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

* cookie name, path or domain inject extra attributes or split the response header. Whether the values are
* otherwise well-formed is left to the caller.
*/
private const PATTERN_HEADER_INJECTION = '/[\x00-\x1F\x7F\x3B]/';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should it include = as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

= only matters for cookieName, but this pattern also validates path and domain, where = is. This class doesn't aim to fully validate cookie attributes against the RFC — only to keep the response header safe from
injection. So = doesn't belong here.

Comment thread src/CsrfTokenCookieMiddleware.php Outdated
{
$response = $handler->handle($request);

return $response->withAddedHeader(Header::SET_COOKIE, $this->buildCookieHeaderValue());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Likely needs Cache-Control/Vary for GET to mark the response as non-cacheable.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

…hen not set

Make the `$secure` constructor parameter `?bool` defaulting to `null`. When
`null`, the `Secure` cookie attribute is resolved per request from the request
URI scheme (added when the scheme is `https`).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D9c67jXQWZ9kKSbzoV747f
By default the middleware now sends `Cache-Control: no-store` for responses
that publish the token and carry no `Cache-Control` of their own, keeping the
token out of shared and private caches. The `$cacheControl` constructor
argument accepts `true` (default), `false` to leave the header untouched, or a
string to set an explicit value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D9c67jXQWZ9kKSbzoV747f
@vjik
vjik requested review from samdark and terabytesoftw and a balanced review from Copilot August 31, 2026 06:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

src/CsrfTokenCookieMiddleware.php:95

  • Issue #99 explicitly requires the cookie's HttpOnly option to be configurable, but the new constructor exposes no such option and the generated header can never include HttpOnly. Add a boolean constructor option (defaulting to false for the JavaScript-readable use case) and apply it when building the cookie header.
        ?string $domain = null,
        ?bool $secure = null,
        ?string $sameSite = self::SAME_SITE_LAX,
        $cacheControl = true

Comment thread src/CsrfTokenCookieMiddleware.php Outdated
Comment thread src/CsrfTokenCookieMiddleware.php Outdated
vjik and others added 3 commits August 31, 2026 09:11
When SameSite=None was combined with the default secure=null and the request
came over HTTP, secure resolved to false and the cookie was emitted without
the Secure attribute, which browsers reject. An explicit secure value still
wins; when it is null, SameSite=None now resolves secure to true, otherwise
the request URI scheme decides.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D9c67jXQWZ9kKSbzoV747f
The middleware makes a response user-specific by publishing a per-user token,
so a previously cacheable `Cache-Control` (for example `public`) is no longer
safe and a shared cache could replay one user's token to another. With
`$cacheControl === true` or a string, `Cache-Control` is now replaced rather
than kept when the response already has one; callers that manage caching
themselves can still pass `false`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D9c67jXQWZ9kKSbzoV747f
@vjik
vjik requested a balanced review from Copilot August 31, 2026 06:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/CsrfTokenCookieMiddleware.php:96

  • Issue #99 explicitly requires the response-cookie feature to support a configurable HttpOnly option, but the constructor exposes no such setting and cookie serialization never emits the attribute. Add an option (defaulting to false so the SPA use case remains JavaScript-readable), serialize it, and document/test both values.
        ?bool $secure = null,
        ?string $sameSite = self::SAME_SITE_LAX,
        $cacheControl = true

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

Labels

status:code review The pull request needs review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provide way to publish the CSRF token in a response cookie.

4 participants