csrf icon indicating copy to clipboard operation
csrf copied to clipboard

Add Custom Request Headers for AJAX/API

Open olegbaturin opened this issue 1 year ago • 4 comments

https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#employing-custom-request-headers-for-ajaxapi

olegbaturin avatar May 15 '24 11:05 olegbaturin

By fact, we need two logic for CSRF middleware:

  1. classic (already implemented)
  2. alternative with check header only.

Suggest create yet another middleware. Something like that:

final class CsrfHeaderMiddleware implements MiddlewareInterface
{
    public const HEADER_NAME = 'X-CSRF-Token';
    private string $headerName = self::HEADER_NAME;

    private ResponseFactoryInterface $responseFactory;
    private ?RequestHandlerInterface $failureHandler;

    public function __construct(
        ResponseFactoryInterface $responseFactory,
        RequestHandlerInterface $failureHandler = null
    ) {
        $this->responseFactory = $responseFactory;
        $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 withHeaderName(string $name): self
    {
        $new = clone $this;
        $new->headerName = $name;
        return $new;
    }

    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;
        }

        $headers = $request->getHeader($this->headerName);
        return (bool) count($headers);
    }
}

What do you think?

vjik avatar May 20 '24 14:05 vjik

Separate middleware makes sense. Could be used per-route.

samdark avatar May 21 '24 06:05 samdark

What's next? Is it approved solution and who will make the PR?

olegbaturin avatar Aug 23 '24 09:08 olegbaturin

What's next? Is it approved solution and who will make the PR?

If possible, you can do a PR.

vjik avatar Aug 23 '24 11:08 vjik