1

I am trying to save a customer shopping cart in a session on the server side. The customer does not need to log in. So no Laravel Sanctum or Passport etc. Laravel and Vue are running on different servers (Backend: localhost:8000 and Frontend: localhost:9000).

Here are my api.php routes.

Route::get('get-token', function (Request $request) {
    return [
        '_token' => $request->session()->token()
    ];    
})->middleware('web');

Route::group(['prefix'=>'v1'], function() {
    Route::resource('cart', App\Http\Controllers\Api\V1\CartController::class)->middleware('web');
    Route::resource('products', App\Http\Controllers\Api\V1\ProductController::class);
}

The GET /cart requests all work fine via axios and insomnia:

here is the corospond CartController function index:

function index() {
    return request()->session()->get('cart');
}

Now the problem

But POST request via axios are rejected with 419 CSRF token missmatch.

Strangely enough, I can make a POST request via Insomnia. By first requesting the token from the server and then specifying _token in the body of the POST request.

> POST /api/v1/cart HTTP/1.1
> Host: localhost:8000
> User-Agent: insomnia/2021.5.3
> Cookie: laravel_session=ZzbpJsfvV22NjO3TB5C3ekiCIp7hlPAcaaZHTkfU; XSRF-TOKEN=eyJpdiI6IjhvVFpWMWtwMm0vU1RKNHB2VzdMdUE9PSIsInZhbHVlIjoiaDNEWTlhcTBRTWI0aXhkaU54Z3I5VndoWFg4Q1A1WmpJUDI1S2hFRGtiMUUwYUl0OEN6S0Q3RVFDZDJXSTNvdTllUXMzN0o1RU94blpqVTdpdTcxaTVvZjlwdFk2Z3djeldteHp0R3Q4UEVjMEovbGk2SHJnVXZmbzRBUzI1RHkiLCJtYWMiOiI2MDMyYzFjNWVjODY4NjRjMTk4NGNmMmI3Yjg4M2VjYzU5YzcwZGRkNDIxMTRiOTc1N2FkMmQ2NTc4YTA5MjhiIiwidGFnIjoiIn0%3D
> Content-Type: multipart/form-data; boundary=X-INSOMNIA-BOUNDARY
> Accept: application/json
> Content-Length: 293

| --X-INSOMNIA-BOUNDARY
| Content-Disposition: form-data; name="product_id"
| 1
| --X-INSOMNIA-BOUNDARY
| Content-Disposition: form-data; name="amount"
| 1
| --X-INSOMNIA-BOUNDARY
| Content-Disposition: form-data; name="_token"
| YgKRkQc9D5GWOPiP8zqVEcoA4FuvESf02SSjy7U3
| --X-INSOMNIA-BOUNDARY--

One feature I absolutely don't understand why I work with the web middleweare in the api route. The api middleware would generate this 500 error message for me:

"message": "Session store not set on request.",

And here is my axios request:

        axios.get('http://localhost:8000/api/get-token').then(response => {
            const _token = response.data._token
            const bodyFormData = new FormData()
            bodyFormData.append('product_id', product.id)
            bodyFormData.append('_token', _token)
            const headers = {
              'Content-Type': 'multipart/form-data',
              'Access-Control-Allow-Credentials': true,
              '_token': _token             
            }

            axios({
              method: "post",
              url: 'cart',
              data: bodyFormData,
              headers: headers,
            }).then((res) => {
              console.log('res',res)
            }).catch((err) => {console.log(err)})

        }); 

This is really a long text. I hope I have given all the necessary information.

2 Answers 2

1
  1. One feature I absolutely don't understand why I work with the web middleweare in the api route.
  • It is because API routes do not have session enabled for the application, because it expects application to use Tokens, so in order to use sessions, you need to have "Web" middleware

Token mismatch error would be because generated/provided token through get-token endpoint wouldn't be the correct one

  • Solution I see would be to add /cart route as an exception route for CSRFTokenMatch, to do that open App/Http/Middleware/VerifyCsrfToken.php file and add route in protected $except array. This is not an ideal thing to do but this will solve your problem for now and help you to move forward.

Another option to make your application secure is to use Laravel Sanctum and generate a token for every guest user (Using Guest Model), if you need help with that I can explain more, but its a little different scenario

Sign up to request clarification or add additional context in comments.

7 Comments

Ravel Thank you for the first quick fix, I put the api/v1/cart route in the $exept array. And it works that I can bring the requests to the server. My problem is that it sees every other request as new. Which means I don't have a unique shopping cart but many sessions. When I reload the page, there is nothing in the cart. Do you have another quick fix ;-) ?
Yes, that is somewhat tricky with session for sure, but let me give you some hints, though I am not sure about whether it will work or not.
1. You take a session id from your request using request()->session()->getId() and then save this session in your vue application using localStorage.setItem('session_id', res.data.sessionId)
2. send session id in your axios post request using bodyFormData.append('session_id', localStorage.getItem('session_id'))
3. now on Laravel Side, try re-init session, by request()->session()->setId(request('session_id'))
|
0

My first guess would simply be that you aren't sending cookies, and need to do

axios.defaults.withCredentials = true

or, more secure (as it limits withCredentials to when you actually want to use it):

const axiosWithCredentials = axios.create({
     withCredentials: true
});
axiosWithCredentials.get('http://localhost:8000/api/get-token').then(response => {
        const _token = response.data._token
        const bodyFormData = new FormData()
        bodyFormData.append('product_id', product.id)
        bodyFormData.append('_token', _token)
        const headers = {
          'Content-Type': 'multipart/form-data',
          'Access-Control-Allow-Credentials': true,
          '_token': _token             
        }

        axiosWithCredentials({
          method: "post",
          url: 'cart',
          data: bodyFormData,
          headers: headers,
        }).then((res) => {
          console.log('res',res)
        }).catch((err) => {console.log(err)})

    }); 

2 Comments

Thanks dave. Still the same response HTTP Status = 419
Ok, well looking further, try not setting the Content-Type header at all and let axios figure things out - all multipart types share a common syntax and include a boundary parameter as part of the media type value, which you don't have. But you don't need it here anyways, axios should figure it out as long as you don't manually set it

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.