0

Final Class Yiisoft\Csrf\CsrfTokenCookieMiddleware

InheritanceYiisoft\Csrf\CsrfTokenCookieMiddleware
ImplementsPsr\Http\Server\MiddlewareInterface

PSR-15 middleware that publishes the current CSRF token in a JavaScript-readable response cookie.

It is intended for AJAX/SPA clients that read the token from a cookie and send it back explicitly in a request header (the "cookie-to-header" pattern), for example Inertia and Axios which use the XSRF-TOKEN cookie and the X-XSRF-TOKEN header.

The middleware only writes the cookie. Token validation stays the responsibility of {@see \Yiisoft\Csrf\CsrfTokenMiddleware}, which reads the submitted token from a header or a body parameter. Place this middleware before {@see \Yiisoft\Csrf\CsrfTokenMiddleware} in the stack, so the cookie is published even on a rejected request.

A Set-Cookie header does not by itself prevent a response from being cached (RFC 9111 section 7.3). A cached response may replay a stale token, or reach a shared (proxy or CDN) cache and hand one user's token to another. By default the middleware guards against this by setting Cache-Control: no-store on every response it touches; see the $cacheControl constructor parameter to change or disable this.

Public Methods

Hide inherited methods

Method Description Defined By
__construct() The cookie name, path and domain are checked only for control characters and ; to prevent response header injection; making sure they are otherwise valid is up to the caller. Yiisoft\Csrf\CsrfTokenCookieMiddleware
process() Yiisoft\Csrf\CsrfTokenCookieMiddleware

Constants

Hide inherited constants

Constant Value Description Defined By
PATTERN_HEADER_INJECTION '/[\x00-\x1F\x7F\x3B]/' Control characters (including CR and LF) and the ; attribute separator. These would let a configured 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. Yiisoft\Csrf\CsrfTokenCookieMiddleware
SAME_SITE_LAX 'Lax' Yiisoft\Csrf\CsrfTokenCookieMiddleware
SAME_SITE_NONE 'None' Yiisoft\Csrf\CsrfTokenCookieMiddleware
SAME_SITE_STRICT 'Strict' Yiisoft\Csrf\CsrfTokenCookieMiddleware

Method Details

Hide inherited methods

__construct() public method

The cookie name, path and domain are checked only for control characters and ; to prevent response header injection; making sure they are otherwise valid is up to the caller.

public mixed __construct ( Yiisoft\Csrf\CsrfTokenInterface $token, string $cookieName = self::COOKIE_NAME, string $path = '/', string|null $domain = null, boolean|null $secure = null, string|null $sameSite = self::SAME_SITE_LAX, boolean|string $cacheControl = true )
$token Yiisoft\Csrf\CsrfTokenInterface

The CSRF token to publish.

$cookieName string

The name of the cookie holding the token.

$path string

The Path attribute of the cookie.

$domain string|null

The Domain attribute of the cookie, or null to omit it.

$secure boolean|null

Whether the cookie should only be sent over HTTPS. A non-null value is used as given. When null, it is resolved automatically: true for self::SAME_SITE_NONE (browsers require Secure for such cookies), otherwise from the request URI scheme (true when the scheme is https).

$sameSite string|null

The SameSite attribute of the cookie: one of self::SAME_SITE_LAX, self::SAME_SITE_STRICT, self::SAME_SITE_NONE, or null to omit it. When self::SAME_SITE_NONE is used, $secure must not be false.

$cacheControl boolean|string

How to handle the Cache-Control response header: true to set it to no-store (keeping the published token out of any cache), false to leave the header untouched, or a string to set it to that exact value. true and a string value replace an existing Cache-Control.

throws InvalidArgumentException

When a cookie attribute or the $cacheControl argument is not valid.

                public function __construct(
    CsrfTokenInterface $token,
    string $cookieName = self::COOKIE_NAME,
    string $path = '/',
    ?string $domain = null,
    ?bool $secure = null,
    ?string $sameSite = self::SAME_SITE_LAX,
    $cacheControl = true
) {
    if (preg_match(self::PATTERN_HEADER_INJECTION, $cookieName)) {
        throw new InvalidArgumentException(
            sprintf('The cookie name "%s" contains invalid characters.', $cookieName),
        );
    }
    if (preg_match(self::PATTERN_HEADER_INJECTION, $path)) {
        throw new InvalidArgumentException(
            sprintf('The cookie path "%s" contains invalid characters.', $path),
        );
    }
    if ($domain !== null && preg_match(self::PATTERN_HEADER_INJECTION, $domain)) {
        throw new InvalidArgumentException(
            sprintf('The cookie domain "%s" contains invalid characters.', $domain),
        );
    }
    $allowedSameSite = [self::SAME_SITE_LAX, self::SAME_SITE_STRICT, self::SAME_SITE_NONE];
    if ($sameSite !== null && !in_array($sameSite, $allowedSameSite, true)) {
        throw new InvalidArgumentException(
            sprintf('The "SameSite" attribute value "%s" is not valid.', $sameSite),
        );
    }
    if ($sameSite === self::SAME_SITE_NONE && $secure === false) {
        throw new InvalidArgumentException(
            'The "secure" flag is required for cookies with "SameSite" attribute set to "None".',
        );
    }
    /**
     * The parameter has no native type because a `bool|string` union is not available on PHP 7.4, so a value of
     * any other type can still be passed at runtime. Once the minimum PHP version is 8.0+, type the parameter as
     * `bool|string` and remove this check together with the suppression below.
     *
     * @psalm-suppress DocblockTypeContradiction
     */
    if (!is_bool($cacheControl) && !is_string($cacheControl)) {
        throw new InvalidArgumentException(
            sprintf('The "cacheControl" argument must be a bool or a string, "%s" given.', gettype($cacheControl)),
        );
    }
    $this->token = $token;
    $this->cookieName = $cookieName;
    $this->path = $path;
    $this->domain = $domain;
    $this->secure = $secure;
    $this->sameSite = $sameSite;
    $this->cacheControl = $cacheControl;
}

            
process() public method

public \Psr\Http\Message\ResponseInterface process ( \Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Server\RequestHandlerInterface $handler )
$request \Psr\Http\Message\ServerRequestInterface
$handler \Psr\Http\Server\RequestHandlerInterface

                public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
    $response = $handler->handle($request);
    $response = $response->withAddedHeader(Header::SET_COOKIE, $this->buildCookieHeaderValue($request));
    return $this->applyCacheControl($response);
}