diff --git a/CHANGELOG.md b/CHANGELOG.md index d417dd3..a2a3c2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2.1.2 under development -- no changes in this release. +- Chg #70: Deprecate `CsrfMiddleware` in favor of `CsrfTokenMiddleware` (@ev-gor) ## 2.1.1 May 08, 2024 diff --git a/README.md b/README.md index 655ff66..1b9a84c 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ composer require yiisoft/csrf ## General usage -In order to enable CSRF protection you need to add `CsrfMiddleware` to your main middleware stack. +In order to enable CSRF protection you need to add `CsrfTokenMiddleware` to your main middleware stack. In Yii it is done by configuring `config/web/application.php`: ```php @@ -48,7 +48,7 @@ return [ [ ErrorCatcher::class, SessionMiddleware::class, - CsrfMiddleware::class, // <-- add this + CsrfTokenMiddleware::class, // <-- add this Router::class, ] ); @@ -74,7 +74,7 @@ You can change this behavior by implementing your own request handler: use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use Yiisoft\Csrf\CsrfMiddleware; +use Yiisoft\Csrf\CsrfTokenMiddleware; /** * @var Psr\Http\Message\ResponseFactoryInterface $responseFactory @@ -99,7 +99,7 @@ $failureHandler = new class ($responseFactory) implements RequestHandlerInterfac } }; -$middleware = new CsrfMiddleware($responseFactory, $csrfToken, $failureHandler); +$middleware = new CsrfTokenMiddleware($responseFactory, $csrfToken, $failureHandler); ``` ## CSRF Tokens diff --git a/src/CsrfMiddleware.php b/src/CsrfMiddleware.php index cffab6c..679ff1d 100644 --- a/src/CsrfMiddleware.php +++ b/src/CsrfMiddleware.php @@ -19,6 +19,7 @@ * PSR-15 middleware that takes care of token validation. * * @link https://www.php-fig.org/psr/psr-15/ + * @deprecated Use the {@see CsrfTokenMiddleware} class instead. */ final class CsrfMiddleware implements MiddlewareInterface { diff --git a/src/CsrfTokenMiddleware.php b/src/CsrfTokenMiddleware.php new file mode 100644 index 0000000..0ef2b3b --- /dev/null +++ b/src/CsrfTokenMiddleware.php @@ -0,0 +1,109 @@ +responseFactory = $responseFactory; + $this->token = $token; + $this->failureHandler = $failureHandler; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($this->validateCsrfToken($request)) { + return $handler->handle($request); + } + + if ($this->failureHandler !== null) { + return $this->failureHandler->handle($request); + } + + $response = $this->responseFactory->createResponse(Status::UNPROCESSABLE_ENTITY); + $response + ->getBody() + ->write(Status::TEXTS[Status::UNPROCESSABLE_ENTITY]); + return $response; + } + + public function withParameterName(string $name): self + { + $new = clone $this; + $new->parameterName = $name; + return $new; + } + + public function withHeaderName(string $name): self + { + $new = clone $this; + $new->headerName = $name; + return $new; + } + + public function getParameterName(): string + { + return $this->parameterName; + } + + public function getHeaderName(): string + { + return $this->headerName; + } + + private function validateCsrfToken(ServerRequestInterface $request): bool + { + if (in_array($request->getMethod(), [Method::GET, Method::HEAD, Method::OPTIONS], true)) { + return true; + } + + $token = $this->getTokenFromRequest($request); + + return !empty($token) && $this->token->validate($token); + } + + private function getTokenFromRequest(ServerRequestInterface $request): ?string + { + $parsedBody = $request->getParsedBody(); + + $token = $parsedBody[$this->parameterName] ?? null; + if (empty($token)) { + $headers = $request->getHeader($this->headerName); + $token = reset($headers); + } + + return is_string($token) ? $token : null; + } +} diff --git a/tests/CsrfMiddlewareTest.php b/tests/CsrfMiddlewareTest.php index 95a83fd..cc0847e 100644 --- a/tests/CsrfMiddlewareTest.php +++ b/tests/CsrfMiddlewareTest.php @@ -6,7 +6,7 @@ use Nyholm\Psr7\Factory\Psr17Factory; use PHPUnit\Framework\TestCase; -use Yiisoft\Csrf\CsrfMiddleware; +use Yiisoft\Csrf\CsrfTokenMiddleware; use Yiisoft\Csrf\Synchronizer\Generator\RandomCsrfTokenGenerator; use Yiisoft\Csrf\Synchronizer\SynchronizerCsrfToken; use Yiisoft\Csrf\Tests\Synchronizer\Storage\MockCsrfTokenStorage; @@ -16,7 +16,7 @@ final class CsrfMiddlewareTest extends TestCase public function testDefaultParameterName(): void { $middleware = $this->createMiddleware(); - $this->assertSame(CsrfMiddleware::PARAMETER_NAME, $middleware->getParameterName()); + $this->assertSame(CsrfTokenMiddleware::PARAMETER_NAME, $middleware->getParameterName()); } public function testGetParameterName(): void @@ -30,7 +30,7 @@ public function testGetParameterName(): void public function testDefaultHeaderName(): void { $middleware = $this->createMiddleware(); - $this->assertSame(CsrfMiddleware::HEADER_NAME, $middleware->getHeaderName()); + $this->assertSame(CsrfTokenMiddleware::HEADER_NAME, $middleware->getHeaderName()); } public function testGetHeaderName(): void @@ -48,9 +48,9 @@ public function testImmutability(): void $this->assertNotSame($original, $original->withParameterName('csrf')); } - private function createMiddleware(): CsrfMiddleware + private function createMiddleware(): CsrfTokenMiddleware { - return new CsrfMiddleware( + return new CsrfTokenMiddleware( new Psr17Factory(), new SynchronizerCsrfToken( new RandomCsrfTokenGenerator(),