Skip to content

Commit

Permalink
rename CsrfMiddleware to CsrfTokenMiddleware
Browse files Browse the repository at this point in the history
  • Loading branch information
olegbaturin committed Nov 8, 2024
1 parent 18d5561 commit 7e15fee
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 40 deletions.
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,30 +103,30 @@ $failureHandler = new class ($responseFactory) implements RequestHandlerInterfac
$middleware = new CsrfTokenMiddleware($responseFactory, $csrfToken, $failureHandler);
```

By default, `CsrfMiddleware` considers `GET`, `HEAD`, `OPTIONS` methods as safe operations and doesn't perform CSRF validation. You can change this behavior as follows:
By default, `CsrfTokenMiddleware` considers `GET`, `HEAD`, `OPTIONS` methods as safe operations and doesn't perform CSRF validation. You can change this behavior as follows:

```php
use Yiisoft\Csrf\CsrfMiddleware;
use Yiisoft\Csrf\CsrfTokenMiddleware;
use Yiisoft\Http\Method;

$csrfMiddleware = $container->get(CsrfMiddleware::class);
$csrfTokenMiddleware = $container->get(CsrfTokenMiddleware::class);

// Returns a new instance with the specified list of safe methods.
$csrfMiddleware = $csrfMiddleware->withSafeMethods([Method::OPTIONS]);
$csrfTokenMiddleware = $csrfTokenMiddleware->withSafeMethods([Method::OPTIONS]);

// Returns a new instance with the specified header name.
$csrfMiddleware = $csrfMiddleware->withHeaderName('X-CSRF-PROTECTION');
$csrfTokenMiddleware = $csrfTokenMiddleware->withHeaderName('X-CSRF-PROTECTION');
```

or define the `CsrfMiddleware` configuration in the DI container:
or define the `CsrfTokenMiddleware` configuration in the DI container:

```php
// [yiisoft/di](https://github.com/yiisoft/di) configuration file example
use Yiisoft\Csrf\CsrfMiddleware;
use Yiisoft\Csrf\CsrfTokenMiddleware;
use Yiisoft\Http\Method;

return [
CsrfMiddleware::class => [
CsrfTokenMiddleware::class => [
'withSafeMethods()' => [[Method::OPTIONS]],
'withHeaderName()' => ['X-CSRF-PROTECTION'],
],
Expand Down Expand Up @@ -273,7 +273,7 @@ return [

The use of a custom request header for CSRF protection is based on the CORS Protocol. Thus, you **must** configure the CORS module to allow or deny cross-origin access to the backend API.

> **Warning**
>**Warning**
>`CsrfHeaderMiddleware` can be used to prevent forgery of same-origin requests and requests from the list of specific origins only.

Expand Down Expand Up @@ -403,24 +403,24 @@ Access-Control-Allow-Origin: $frontendOrigin

#### Configure middlewares stack

By default, `CsrfMiddleware` considers `GET`, `HEAD`, `OPTIONS` methods as safe operations and doesn't perform CSRF validation.
By default, `CsrfTokenMiddleware` considers `GET`, `HEAD`, `OPTIONS` methods as safe operations and doesn't perform CSRF validation.
In JavaScript-based apps, requests are made programmatically; therefore, to increase application protection, the only `OPTIONS` method can be considered safe and need not be appended with a CSRF token header.

Configure `CsrfMiddleware` safe methods:
Configure `CsrfTokenMiddleware` safe methods:

```php
$csrfMiddleware = $container->get(CsrfMiddleware::class);
$csrfMiddleware = $csrfMiddleware->withSafeMethods([Method::OPTIONS]);
$csrfTokenMiddleware = $container->get(CsrfTokenMiddleware::class);
$csrfTokenMiddleware = $csrfTokenMiddleware->withSafeMethods([Method::OPTIONS]);
```

Add `CsrfMiddleware` to the main middleware stack:
Add `CsrfTokenMiddleware` to the main middleware stack:

```php
$middlewareDispatcher = $injector->make(MiddlewareDispatcher::class);
$middlewareDispatcher = $middlewareDispatcher->withMiddlewares([
ErrorCatcher::class,
SessionMiddleware::class,
CsrfMiddleware::class, // <-- add this
CsrfTokenMiddleware::class, // <-- add this
Router::class,
]);
```
Expand All @@ -431,7 +431,7 @@ or to the routes that must be protected:
$collector = $container->get(RouteCollectorInterface::class);
$collector->addGroup(
Group::create('/api')
->middleware(CsrfMiddleware::class) // <-- add this
->middleware(CsrfTokenMiddleware::class) // <-- add this
->routes($routes)
);
```
Expand Down Expand Up @@ -476,7 +476,7 @@ let csrfToken = await response.text();
let csrfToken = response.headers.get('X-CSRF-TOKEN');
```

Add to all requests a custom header defined in the `CsrfMiddleware` with acquired CSRF-token value.
Add to all requests a custom header defined in the `CsrfTokenMiddleware` with acquired CSRF-token value.

```js
let response = fetch('https://api.example.com/whoami', {
Expand Down
5 changes: 5 additions & 0 deletions src/CsrfHeaderMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ final class CsrfHeaderMiddleware implements MiddlewareInterface
public const HEADER_NAME = 'X-CSRF-Header';

private string $headerName = self::HEADER_NAME;

/**
* @var array "unsafe" methods not triggered a CORS-preflight request
* @link https://fetch.spec.whatwg.org/#http-cors-protocol
*/
private array $unsafeMethods = [Method::GET, Method::HEAD, Method::POST];

private ResponseFactoryInterface $responseFactory;
Expand Down
5 changes: 5 additions & 0 deletions src/CsrfMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ final class CsrfMiddleware implements MiddlewareInterface

private string $parameterName = self::PARAMETER_NAME;
private string $headerName = self::HEADER_NAME;

/**
* @var array "safe" methods skipped on CSRF token validation
* @link https://datatracker.ietf.org/doc/html/rfc9110#name-safe-methods
*/
private array $safeMethods = [Method::GET, Method::HEAD, Method::OPTIONS];

private ResponseFactoryInterface $responseFactory;
Expand Down
15 changes: 14 additions & 1 deletion src/CsrfTokenMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ final class CsrfTokenMiddleware implements MiddlewareInterface
private string $parameterName = self::PARAMETER_NAME;
private string $headerName = self::HEADER_NAME;

/**
* @var array "safe" methods skipped on CSRF token validation
* @link https://datatracker.ietf.org/doc/html/rfc9110#name-safe-methods
*/
private array $safeMethods = [Method::GET, Method::HEAD, Method::OPTIONS];

private ResponseFactoryInterface $responseFactory;
private CsrfTokenInterface $token;
private ?RequestHandlerInterface $failureHandler;
Expand Down Expand Up @@ -73,6 +79,13 @@ public function withHeaderName(string $name): self
return $new;
}

public function withSafeMethods(array $methods): self
{
$new = clone $this;
$new->safeMethods = $methods;
return $new;
}

public function getParameterName(): string
{
return $this->parameterName;
Expand All @@ -85,7 +98,7 @@ public function getHeaderName(): string

private function validateCsrfToken(ServerRequestInterface $request): bool
{
if (in_array($request->getMethod(), [Method::GET, Method::HEAD, Method::OPTIONS], true)) {
if (in_array($request->getMethod(), $this->safeMethods, true)) {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,36 @@
use Yiisoft\Http\Status;
use Yiisoft\Security\Random;

abstract class TokenCsrfMiddlewareTest extends TestCase
abstract class CsrfTokenMiddlewareProcessTest extends TestCase
{
private const PARAM_NAME = 'csrf';

private string $token;

public function testGetIsAlwaysAllowed(): void
{
$middleware = $this->createCsrfMiddleware();
$middleware = $this->createCsrfTokenMiddleware();
$response = $middleware->process($this->createServerRequest(Method::GET), $this->createRequestHandler());
$this->assertEquals(200, $response->getStatusCode());
}

public function testHeadIsAlwaysAllowed(): void
{
$middleware = $this->createCsrfMiddleware();
$middleware = $this->createCsrfTokenMiddleware();
$response = $middleware->process($this->createServerRequest(Method::HEAD), $this->createRequestHandler());
$this->assertEquals(200, $response->getStatusCode());
}

public function testOptionsIsAlwaysAllowed(): void
{
$middleware = $this->createCsrfMiddleware();
$middleware = $this->createCsrfTokenMiddleware();
$response = $middleware->process($this->createServerRequest(Method::OPTIONS), $this->createRequestHandler());
$this->assertEquals(200, $response->getStatusCode());
}

public function testValidTokenInBodyPostRequestResultIn200(): void
{
$middleware = $this->createCsrfMiddleware();
$middleware = $this->createCsrfTokenMiddleware();
$response = $middleware->process(
$this->createPostServerRequestWithBodyToken($this->token),
$this->createRequestHandler()
Expand All @@ -57,7 +57,7 @@ public function testValidTokenInBodyPostRequestResultIn200(): void

public function testValidTokenInBodyPutRequestResultIn200(): void
{
$middleware = $this->createCsrfMiddleware();
$middleware = $this->createCsrfTokenMiddleware();
$response = $middleware->process(
$this->createPutServerRequestWithBodyToken($this->token),
$this->createRequestHandler()
Expand All @@ -67,7 +67,7 @@ public function testValidTokenInBodyPutRequestResultIn200(): void

public function testValidTokenInBodyDeleteRequestResultIn200(): void
{
$middleware = $this->createCsrfMiddleware();
$middleware = $this->createCsrfTokenMiddleware();
$response = $middleware->process(
$this->createDeleteServerRequestWithBodyToken($this->token),
$this->createRequestHandler()
Expand All @@ -77,7 +77,7 @@ public function testValidTokenInBodyDeleteRequestResultIn200(): void

public function testValidTokenInHeaderResultIn200(): void
{
$middleware = $this->createCsrfMiddleware();
$middleware = $this->createCsrfTokenMiddleware();
$response = $middleware->process(
$this->createPostServerRequestWithHeaderToken($this->token),
$this->createRequestHandler()
Expand All @@ -90,7 +90,7 @@ public function testValidTokenInCustomHeaderResultIn200(): void
$headerName = 'CUSTOM-CSRF';

$middleware = $this
->createCsrfMiddleware()
->createCsrfTokenMiddleware()
->withHeaderName($headerName);
$response = $middleware->process(
$this->createPostServerRequestWithHeaderToken($this->token, $headerName),
Expand All @@ -102,7 +102,7 @@ public function testValidTokenInCustomHeaderResultIn200(): void

public function testInvalidTokenResultIn422(): void
{
$middleware = $this->createCsrfMiddleware();
$middleware = $this->createCsrfTokenMiddleware();

$response = $middleware->process(
$this->createPostServerRequestWithBodyToken(Random::string()),
Expand All @@ -126,7 +126,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface
}
};

$middleware = $this->createCsrfMiddleware(null, $failureHandler);
$middleware = $this->createCsrfTokenMiddleware(null, $failureHandler);

$response = $middleware->process(
$this->createPostServerRequestWithBodyToken(Random::string()),
Expand All @@ -139,14 +139,14 @@ public function handle(ServerRequestInterface $request): ResponseInterface

public function testEmptyTokenInRequestResultIn422(): void
{
$middleware = $this->createCsrfMiddleware();
$middleware = $this->createCsrfTokenMiddleware();
$response = $middleware->process($this->createServerRequest(), $this->createRequestHandler());
$this->assertEquals(Status::UNPROCESSABLE_ENTITY, $response->getStatusCode());
}

public function testUnsafeMethodPostRequestResultIn422(): void
{
$middleware = $this->createCsrfMiddleware();
$middleware = $this->createCsrfTokenMiddleware();
$response = $middleware->process(
$this->createServerRequest(Method::POST),
$this->createRequestHandler()
Expand All @@ -158,7 +158,7 @@ public function testUnsafeMethodPostRequestResultIn422(): void
public function testCustomSafeOptionsRequestResultIn200(): void
{
$middleware = $this
->createCsrfMiddleware()
->createCsrfTokenMiddleware()
->withSafeMethods([Method::OPTIONS]);
$response = $middleware->process(
$this->createServerRequest(Method::OPTIONS),
Expand All @@ -170,7 +170,7 @@ public function testCustomSafeOptionsRequestResultIn200(): void
public function testCustomUnsafeMethodGetRequestResultIn422(): void
{
$middleware = $this
->createCsrfMiddleware()
->createCsrfTokenMiddleware()
->withSafeMethods([Method::OPTIONS]);
$response = $middleware->process(
$this->createServerRequest(Method::GET),
Expand Down Expand Up @@ -230,7 +230,7 @@ private function getBodyRequestParamsByToken(string $token): array
];
}

protected function createCsrfMiddleware(
protected function createCsrfTokenMiddleware(
?CsrfTokenInterface $csrfToken = null,
RequestHandlerInterface $failureHandler = null
): CsrfTokenMiddleware {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use Yiisoft\Csrf\Tests\Synchronizer\Storage\MockCsrfTokenStorage;
use Yiisoft\Http\Method;

final class CsrfMiddlewareTest extends TestCase
final class CsrfTokenMiddlewareTest extends TestCase
{
public function testDefaultParameterName(): void
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
use Yiisoft\Csrf\CsrfTokenInterface;
use Yiisoft\Csrf\Hmac\HmacCsrfToken;
use Yiisoft\Csrf\Tests\Hmac\IdentityGenerator\MockCsrfTokenIdentityGenerator;
use Yiisoft\Csrf\Tests\TokenCsrfMiddlewareTest;
use Yiisoft\Csrf\Tests\CsrfTokenMiddlewareProcessTest;
use Yiisoft\Security\Random;

final class HmacTokenCsrfMiddlewareTest extends TokenCsrfMiddlewareTest
final class HmacTokenCsrfTokenMiddlewareProcessTest extends CsrfTokenMiddlewareProcessTest
{
protected function createCsrfToken(): CsrfTokenInterface
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
use Yiisoft\Csrf\Synchronizer\Generator\RandomCsrfTokenGenerator;
use Yiisoft\Csrf\Synchronizer\SynchronizerCsrfToken;
use Yiisoft\Csrf\Tests\Synchronizer\Storage\MockCsrfTokenStorage;
use Yiisoft\Csrf\Tests\TokenCsrfMiddlewareTest;
use Yiisoft\Csrf\Tests\CsrfTokenMiddlewareProcessTest;
use Yiisoft\Security\Random;

final class SynchronizerTokenCsrfMiddlewareTest extends TokenCsrfMiddlewareTest
final class SynchronizerTokenCsrfTokenMiddlewareProcessTest extends CsrfTokenMiddlewareProcessTest
{
public function testEmptyTokenInSessionResultIn422(): void
{
$middleware = $this->createCsrfMiddleware(
$middleware = $this->createCsrfTokenMiddleware(
new SynchronizerCsrfToken(
new RandomCsrfTokenGenerator(),
new MockCsrfTokenStorage()
Expand Down

0 comments on commit 7e15fee

Please sign in to comment.