createServerRequest('GET', '/login'); $response = $mw->process($request, $this->handler(static function (ServerRequestInterface $req): bool { return is_string($req->getAttribute(CsrfMiddleware::ATTR_TOKEN)) && strlen((string) $req->getAttribute(CsrfMiddleware::ATTR_TOKEN)) === 64; })); self::assertSame(200, $response->getStatusCode()); self::assertNotEmpty($_SESSION[CsrfMiddleware::SESSION_KEY]); } public function testPostWithoutTokenIs403(): void { $mw = new CsrfMiddleware(new ResponseFactory()); $request = (new ServerRequestFactory())->createServerRequest('POST', '/login/local'); $response = $mw->process($request, $this->handler(static fn () => true)); self::assertSame(403, $response->getStatusCode()); } public function testPostWithMatchingFormFieldPasses(): void { $mw = new CsrfMiddleware(new ResponseFactory()); $_SESSION[CsrfMiddleware::SESSION_KEY] = 'fixed-token'; $request = (new ServerRequestFactory()) ->createServerRequest('POST', '/login/local') ->withParsedBody(['csrf_token' => 'fixed-token', 'username' => 'a']); $response = $mw->process($request, $this->handler(static fn () => true)); self::assertSame(200, $response->getStatusCode()); } public function testPostWithMatchingHeaderPasses(): void { $mw = new CsrfMiddleware(new ResponseFactory()); $_SESSION[CsrfMiddleware::SESSION_KEY] = 'fixed-token'; $request = (new ServerRequestFactory()) ->createServerRequest('POST', '/api/x') ->withHeader('X-CSRF-Token', 'fixed-token'); $response = $mw->process($request, $this->handler(static fn () => true)); self::assertSame(200, $response->getStatusCode()); } public function testPostWithWrongTokenIs403(): void { $mw = new CsrfMiddleware(new ResponseFactory()); $_SESSION[CsrfMiddleware::SESSION_KEY] = 'fixed-token'; $request = (new ServerRequestFactory()) ->createServerRequest('POST', '/login/local') ->withParsedBody(['csrf_token' => 'wrong-token']); $response = $mw->process($request, $this->handler(static fn () => true)); self::assertSame(403, $response->getStatusCode()); } /** * @param callable(ServerRequestInterface): bool $assert */ private function handler(callable $assert): RequestHandlerInterface { return new class ($assert) implements RequestHandlerInterface { /** @var callable(ServerRequestInterface): bool */ private $assert; public function __construct(callable $assert) { $this->assert = $assert; } public function handle(ServerRequestInterface $request): ResponseInterface { $ok = ($this->assert)($request); $factory = new ResponseFactory(); $response = $factory->createResponse($ok ? 200 : 418); $stream = (new StreamFactory())->createStream('OK'); return $response->withBody($stream); } }; } }