REST-Api: 403 - what is missing here?

Hi all!

Its me again … :slight_smile:

For new project I’ve set up minimal configuration of a REST-Api.
For authenication I use a bearer token. This works fine for
simple and more complext GET requests.

The problem arises with “unsave request”, in my case i.e. a PUT request.

For testing I use cURL on CLI from another machine.

In my simple implementation there is a method to get a valid CSRF token to use
it for the PUT request. I allways get an 403 error and in system log
I see:

The action could not be executed because you supplied no or the wrong CSRF protection token.

I’m shure there is missing something - but what? May be Im blind… :wink:

Here my test system:

Policy.yaml:

roles:
  'Acme.Rest:PseudoUser':
    privileges:
      -
        privilegeTarget: 'Acme.Rest:PseudoUser.ProductActions'
        permission: GRANT

privilegeTargets:
  'Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilege':

    'Acme.Rest:PseudoUser.ProductActions':
      matcher: 'method(Acme\Rest\Controller\ProductController->.*Action())'

Routes.yaml (excerpt):


-
  name: 'product update'
  uriPattern: 'product/update'
  defaults:
    '@package': 'Acme.Rest'
    '@controller': 'Product'
    '@action': 'update'
    '@format': 'json'
  httpMethods: ['PUT']

ProductController:

class ProductController  extends RestController
{


   /**
    * @Flow\Inject
    * @var Context
    */
   protected $securityContext;

[...]

   public function csrfGetterAction()
   {

      $tokenInfo = [ '__csrfToken' => $this->securityContext->getCsrfProtectionToken() ];
      $this->view->setVariablesToRender( ['tokenInfo'] );
      $this->view->assign('tokenInfo', $tokenInfo );
      $this->response->setStatusCode(200);

   }


  /**
   * public function updateAction
   *
   *  only for testing - this method does nothing! 
   *
   * @param string $productName
   * @return void
   */

   public function updateAction( string $productName )
   {
 
      $this->view->setVariablesToRender( ['result'] );
      $this->view->assign('result', [ "product" => $productName, "b" => 'update/PUT' ] );
      $this->response->setStatusCode(200);
 
   } 

My cURL requests:

First get CSRF token

curl -c cookies.txt -b cookies.txt -i -X GET -H "Authorization: Bearer ...." https://example.com/product/csrf

delivers something like this:


HTTP/1.1 200 OK
Date: Fri, 04 Apr 2025 14:33:47 GMT
Server: Apache/2.4.41 (Ubuntu)
X-Flow-Powered: Flow/8.3
Set-Cookie: Neos_Flow_Session=BnFGiUiu1wLrpWbpcsNh7quqLnFQZoF1; Path=/; HttpOnly; SameSite=lax
Transfer-Encoding: chunked
Content-Type: application/json

{"__csrfToken":"bb674a7d6b757e6a5093375e19046f73"}

now the PUT request:

curl -c cookies.txt -b cookies.txt -i -X PUT -H "Authorization: Bearer ..." -d '{"__csrfToken":"bb674a7d6b757e6a5093375e19046f73","productName":"lambda"}' https://example.com/product/update

delivers:

HTTP/1.1 403 Forbidden
Date: Fri, 04 Apr 2025 14:34:01 GMT
Server: Apache/2.4.41 (Ubuntu)
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

[...]

For to be shure, I’ve checked the token by calling again https://example.com/product/csrf => sends same CSRF token as above!

Now my question: what I missing here?
I’ve spend houres to solve this problem but I’m stuck… :frowning:

Does some have an idea?
Thx for any hint!

Hi @mexoona,
question is, what do you need the CRSF token for? The REST API shouldn’t need that. So you can disable it for your controller/action.

I’m not sure if there is another way, but you can add an annotation to your action:

   /**
    * @Flow\SkipCsrfProtection
    */
   public function updateAction( string $productName )
   {
       $this->view->setVariablesToRender( ['result'] );
      $this->view->assign('result', [ "product" => $productName, "b" => 'update/PUT' ] );
      $this->response->setStatusCode(200);
    }

See: Security — Flow Framework 9.0.x documentation

I had a quick look into this. Authenticated requests, do not need the CSRF token by default. So you should get your request “authenticated” by the security framework with your bearer token.

See: Security — Flow Framework 9.0.x documentation

My guess is that the csrftoken is not read inside the JSON body, BUT we allow supplying it via Headers, which for your API is probably the better solution.
that said the CSRF protection is IMHO relatively meaningless for APIs and you could follow Dennys advice to disable them for these actions via annotation.
If you want to use headers it must be: X-Flow-Csrftoken

Thanks Denny and Christian!

I knew that option to bypass the CSRF check, but I dont have a “good feeling” with that solution. Its just a feeling …
To use the header option is an good idea - this one I did not know!
Will checkt it today and give a feedback here.

Thanks a lot again! :slight_smile:

YES!

CSRF via Header works like a charm! :slight_smile:

Thank you very much!

2 Likes