Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Wonka's avatar
Level 1

Laravel 5.6 - How to authenticate API using sessions for same folder SPA?

I have a React SPA in the same Laravel project. The login/signup/logout and all other js views are in the js folder and use axios api calls for all POST/GET requests. I want to use the default Laravel session based web authentication for the embedded SPA, since it's in the same project folder and it will be the only javascript client accessing it. This api does not need to be open to the public, just for this react app, and it's an SPA for the speed and good user experience instead of full page reloads.

I do not want to deal with Passport tokens, access tokens, refresh tokens, revoking tokens, CSRF, etc. Just the out of the box simple Laravel session based auth that works so easily on web, but want it to work on my react app. The only blade file is the index.blade.php which includes the react app.js

Someone on Stack Overflow guided me to:

You have to add the various Session/Cookie middlewares in app/Http/Kernel.php (stuff like \Illuminate\Session\Middleware\StartSession::class) to the API routes.

Based on that suggestion, I added to $middlewareGroups.api to match the web middleware in app/Http/Kernel.php:

'api' => [
    'throttle:60,1',
    'bindings',
    // Newly added middleware to match web middleware
    \App\Http\Middleware\EncryptCookies::class
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \App\Http\Middleware\VerifyCsrfToken::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

I realized there are two issues that occurred:

  1. In the sessions table, even if not logged in, when loading app home page (or any page), multiple sessions are inserted into the sessions table. Shouldn't a new single session be inserted into this table only after user login?

  2. After user log in, when refreshing the page manually in the browser and a call is made to a protected route, I get a 401 Unauthenticated which points me to this method in Illuminate/Auth/GuardHelpers.php:

     public function authenticate() {
         if (! is_null($user = $this->user())) {
             return $user;
         }
    
         throw new AuthenticationException; // throws this 401 exception on logged in page refresh when fetching data from private route
     }
    

Some additional notes:

  • In config/auth.php I updated the guards.api.driver to session instead of token.

  • In routes/api.php I have the protected routes wrapped in auth middleware like this: Route::group(['middleware' => 'auth'], function() { PRIVATE ROUTES HERE }

  • In config/session.php I have 'domain' => '.mydomain.com'

  • I am sending back these headers with each axios api request like this:

      window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
      let token = document.head.querySelector('meta[name="csrf-token"]');
      window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
    

Any idea how we can fix these 2 issues?

0 likes
12 replies
cmdobueno's avatar

If you want to do this, why are using API routes? You could just web-routes and life would be perfectly fine. You are just accessing the system with ajax/axios instead of the browser... calling this an api is wrong. Use web-routes and you will probably remove the vast majority of your issues.

Wonka's avatar
Level 1

@cmdobueno You mean just move all the api routes to the web routes file, just have auth for the protected routes, and continue using axios for the calls? Do I need to keep passing the X-Requested-With/X-CSRF-TOKEN with the axios requests?

cmdobueno's avatar

You can turn off the token in middleware.

But yet, that is roughly what I am saying... use normal authentication, because that is what you are doing.

API is 'effectively' if not 'completed' stateless. It is kept known by tokens of one kind or another. I have never heard of using state (session authentication with an API... but I by no means an expert).

If i were you, i would keep things wrapped inside of web, because you are using these are web. That may solve all of your problems for the most part

just remember thati f you are using auth:api to change it to auth

1 like
Wonka's avatar
Level 1

@cmdobueno So I put all api routes in web instead, was able to log in via the api by posting to the custom LoginController normally and access auth protected routes with no problem. But as soon as I refresh the page, any api calls to protected routes return the 401 Unauthenticated and the sessions table still adds entries before login even on page load. So basically the 2 issues are still present.

This is so hard to figure out, has been 2 days so far and no clear way of solving it after everything I tried.

jekinney's avatar

@wonka That is not how it is supposed to work at all. When you refresh the page you lose all your data that javascript is using. If your front end is all javascript use api authentication. Which case if you are using auth:api you have access to auth()->user() in the backend. But you still need to set up storing the token (and other data) on the session for your javascript app. literally thousands of tutorials for this.

Here is a basic for backend:

https://medium.com/modulr/create-api-authentication-with-passport-of-laravel-5-6-1dc2d400a7f

I believe Jeffrey has a vue one for the frontend, even with React, the premise is the same.

Wonka's avatar
Level 1

@jekinney So based on this answer (https://stackoverflow.com/a/54210281/485961), since the javascript frontend is on the same domain/part of the laravel project folder, it should be possible to use sessions with the built in auth. Also this reddit thread (https://www.reddit.com/r/laravel/comments/8rrs6s/why_is_it_so_hard_to_authenticate_an_api_using/) says the same, that it should be possible.

The default laravel auth handles the httpOnly/CSRF security really well, but I can't find anywhere online that does the same for passport. The closest thing is (http://esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4/) which shows the httpOnly cookie part but no mention of CSRF. Since we are the only consumer of this api, and it is part of the same project folder, I feel like it should be possible.

With the page refresh, the httpOnly cookie should still be there, since it's in the browser, and I save all fetched data in local stores, with exception to the token, since it's not secure in local storage. So since the browser is what contains the httpOnly cookie, I feel like this should work, just don't know how 100%.

SteveCove's avatar

Sessions get created for every user, logged in or not (needed for csrf field).

If you are seeing multiple sessions in local dev (and you are not using multiple browsers /private tabs), then it looks like the cookie isn't being set properly. What are you seeing in Dev tools?

jlrdw's avatar

@Wonka if you have found the answer, what is the problem?

So based on this answer (https://stackoverflow.com/a/54210281/485961), since the javascript frontend is on the same domain/part of the laravel project folder, it should be possible to use sessions with the built in auth. Also this reddit thread (https://www.reddit.com/r/laravel/comments/8rrs6s/why_is_it_so_hard_to_authenticate_an_api_using/) says the same, that it should be possible...

But I would consider using @jekinney advice he is pretty darn good at this stuff.

Wonka's avatar
Level 1

@stevecove I can see the cookie being set, both XSRF-TOKEN and my_domain_session, for both request and response. Everything seems working until the page refresh, then any protected auth middleware routes ajax GET/POST requests return a 401 Unauthenticated as it seems to fail like in my issue #2 above in the Illuminate/Auth/GuardHelpers.php

Also to note, after page refresh any GET requests to non auth protected routes return data normally. It is just the auth routes that return 401 after page refresh when fetched via axios.

@jlrdw The answer linked only works until you refresh the page, then it doesn't work as stated above when accessing any auth protected endpoints with axios, which is why I posted an update there and posted the issue here as well. But both links seem to show that people have successfully done laravel auth without passport if the js/laravel app are not separate and both in same project, with the js being the only consumer of the api data.

@jekinney @jlrdw When trying Passport initially, I was stuck on it for a month as you can see here (https://stackoverflow.com/questions/53678019/laravel-5-6-passport-jwt-httponly-cookie-spa-authentication-for-self-consuming) and after not having any answer covering the chart in update 3 (which included access/refresh/csrf integration), I had to rethink the authentication, which led me to some people saying they successfully implemented default laravel session based authentication for their js app when it was in the same folder. But when I tried that, it worked perfectly until page refresh. And since the cookies are stored in the browser, I had to post here again to see how to proceed when I hit those 2 issues.

SteveCove's avatar

Its hard to follow what changes you have made - you started by adding middleware to the api routes, then tried just adding all your routes in the routes\web.php file (which is what i would do).

Whats your current configuration?

Wonka's avatar
Level 1

@stevecove all routes are under routes/web.php looks like this:

// api routes
Route::group(['prefix' => 'api'], function() {
    
    Route::group([], function() {
        // Public Routes Here
    });

    Route::group(['middleware' => 'auth'], function() {
        // Private Routes Here
    });

});

// other web routes

Hmmm... so after page refresh, any auth protected ajax calls return 401 (but they work prior to refresh with no issues). When I printed out the Auth::user()->email in index.blade.php it shows no auth email before and after login, on page refresh after login, it shows the user email, but get the 401 ajax issue, then refresh page again and it shows no auth email, and for all future refreshes no auth email, and ofcourse the persistent 401 for ajax calls to auth protected routes, non protected routes always load data perfectly via ajax.

Wonka's avatar
Wonka
OP
Best Answer
Level 1

Good news! I figured it out, so in config/session.php I had the last variable 'same_site' => 'strict' after changing it to 'same_site' => null the 401 error went away, and everything works perfectly.

I really appreciate all the help guys, thank you all so much!

Please or to participate in this conversation.