fakeman2332's avatar

CSRF Token mismatch

Hi! I use NextJS and Laravel with cookie-based authorization (session). (Yes, it probably sounds crazy.)

I am sending a request to /csrf-cookie endpoint, which responds successfully. But the POST request fails and returns a CSRF-token error.

Im using this function:

await axios.get(RETRIEVE_XSRF_TOKEN_URI).then(async csrf => {
    await axios.post(API_BASE_URI + '/discord/unlink');
});
  • The X-XSRF-TOKEN header is given along with the request:
POST /backend/api/public/discord/unlink HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0
Accept: application/json, text/plain, */*
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
X-Requested-With: XMLHttpRequest
X-XSRF-TOKEN: long-token-based-string
Origin: localhost:3000
Connection: keep-alive
Referer: localhost:3000
Cookie: long-cookie-based-string (with XSRF-TOKEN cookie)
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 0```

Axios setup:

const axios = Axios.create({
  headers: {
    'X-Requested-With': 'XMLHttpRequest',
  },
  withCredentials: true,
  withXSRFToken: true,
});

Laravel ENV setup:

APP_URL=localhost:8000
FRONTEND_URL=localhost:3000
SESSION_DOMAIN=.localhost
SANCTUM_STATEFUL_DOMAINS=localhost:3000

CORS setup:

    'paths' => ['backend/*'],

    'allowed_methods' => ['*'],

    'allowed_origins' => [env('FRONTEND_URL', 'localhost:3000')],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => true,

* all domains are starting with http (excluding SESSION_DOMAIN property), I cant provide it due forum restrictions

0 likes
9 replies
alden8's avatar

The issue you're facing might be due to the way you're setting the X-XSRF-TOKEN in your axios instance.

In your axios setup, you have withXSRFToken: true, but axios does not have a withXSRFToken option. Instead, axios has an xsrfCookieName option that defines the name of the cookie to use as a value for X-XSRF-TOKEN.

You should set xsrfCookieName to 'XSRF-TOKEN' and xsrfHeaderName to 'X-XSRF-TOKEN' in your axios instance. Here's how you can do it:

const axios = Axios.create({
  headers: {
    'X-Requested-With': 'XMLHttpRequest',
  },
  withCredentials: true,
  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',
});

This tells axios to read the CSRF token from the XSRF-TOKEN cookie and set it as the X-XSRF-TOKEN header in the request.

Also, make sure that the XSRF-TOKEN cookie is being set correctly in the /csrf-cookie endpoint response, and that it's being sent with the request. You can check this in your browser's developer tools.

If you're still facing issues, you might want to check your Laravel CSRF middleware and make sure it's configured correctly.

1 like
fakeman2332's avatar

@alden8, axios have a withXSRFToken property, without them it not sets the header. I debugged middleware and found strange issue: CSRF token check breaks on tokensMatch method, on hash_equals($request->session()->token(), $token) function. I can't provide screenshots, so

protected function tokensMatch($request)
    {
        // return is_string($request->session()->token()) &&
        //               is_string($token) &&
        //               hash_equals($request->session()->token(), $token);

        $token = parent::getTokenFromRequest($request);
        $match = hash_equals($request->session()->token(), $token);

        Log::info('match = ' . $match);
        Log::info('has header = '. json_encode($request->hasHeader('X-XSRF-TOKEN')));

        return $match;
    }
[2024-01-12 17:19:34] local.INFO: match =   
[2024-01-12 17:19:34] local.INFO: has header = true  
alden8's avatar
alden8
Best Answer
Level 1

@fakeman2332 The POST request fails with a CSRF token error, indicating a mismatch between the token in the request header and the one in the session. Axios Configuration: The withXSRFToken property may not be standard for Axios. Instead, focus on xsrfCookieName and xsrfHeaderName. Middleware Debugging: The tokensMatch method in the Laravel middleware reveals a mismatch, with the log showing match = and has header = true.

  1. Axios Setup
        const axios = Axios.create({
          headers: {
            'X-Requested-With': 'XMLHttpRequest',
          },
          withCredentials: true,
          xsrfCookieName: 'XSRF-TOKEN',
          xsrfHeaderName: 'X-XSRF-TOKEN',
        });

Consider Alternative Approach: If withXSRFToken is necessary for your Axios version, manually set the header from the cookie:

const csrfToken = Cookies.get('XSRF-TOKEN');
await axios.get(RETRIEVE_XSRF_TOKEN_URI); // Ensure this call actually sets the cookie
await axios.post(API_BASE_URI + '/discord/unlink', {}, {
  headers: {
    'X-XSRF-TOKEN': csrfToken,
  },
});
  1. Laravel Middleware:

     -Debugging:
    
     -- Log the actual values of $request->session()->token() and $token in the tokensMatch method to inspect their contents.
     -- Check for potential modifications to the token in other middleware or filters.
    
     - Token Generation: Ensure Laravel generates the CSRF token correctly and stores it in the session.
     - Middleware Configuration: Verify that the CSRF middleware is applied to the appropriate routes in app/Http/Kernel.php.
    
  2. Cookie Handling: - Cookie Domain: Ensure the SESSION_DOMAIN setting in Laravel encompasses both the frontend and backend domains. - Cookie Availability: Confirm that the XSRF-TOKEN cookie is accessible to both the frontend and backend applications. - Cookie Lifetime: Consider adjusting the cookie's lifetime if it expires too quickly.

  3. CORS Configuration: - Double-Check: Review your CORS configuration to ensure it allows the necessary headers and origins.

Troubleshooting Steps:

1) Implement the suggested Axios configuration.
2) Inspect the token values in the Laravel middleware.
3) Verify cookie handling and CORS settings.
4) If the issue persists, provide more details about your environment (Axios and Laravel versions, specific code snippets, etc.) for further assistance.
1 like
fakeman2332's avatar

@alden8 I solved this issue by setting SESSION_DRIVER to file. Previously this property been set to array (I set it to this by answer in google 🤓).

But thank you! :)

2 likes
Snapey's avatar

The csrf token will change frequently. Make sure you are using the most recent token at each request

1 like
skiarsi's avatar

this is exactly my problem, after /sanctum/csrf-cookie , Laravel sanctum will change csrf token then on /api/login we need new csrf token because it's change and it will not work fine and always i'll get error. how should I fix this problem?

    e.preventDefault();

    try {
      await axios.get(
        `${process.env.NEXT_PUBLIC_BACKEND_URL}/sanctum/csrf-cookie`,
        {
          withCredentials: true,
        }
      );

      const response = await axios.post(
        `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/login/`,
        { email, password },
        {
          withCredentials: true,
          headers: {
            "Content-Type": "application/json",
            Accept: "application/json",
          },
        }
      );
      console.log(response);
    } catch {
    } finally {
    }
  };```
shaneomac's avatar

Please or to participate in this conversation.