2

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've tried using Passport before, and for over a month, I still can't get it to work as intended. I do not want to deal with 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

Any idea how we can accomplish this?

UPDATE 1:

After implementing @ceejayoz's suggestion:

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.

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?

Wonka
  • 8,244
  • 21
  • 73
  • 121
  • 1
    It's doable. 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. – ceejayoz Jan 15 '19 at 22:55
  • 2
    If you plan using your routes with `/api` prefix, you can just define them in `routes/web.php` and not in `routes/api.php`. Make sure that you include `auth` middleware. Then you can place them into a group `Route::group(['prefix' => 'api', 'middleware' => ['auth'],], function () {...` – ljubadr Jan 15 '19 at 23:14
  • 1
    @ceejayoz That's actually pretty smart, thanks for sharing! This is SO much easier than implementing Passport which I think would've been an overkill for my use case. If you post an answer I will accept it as it required the least amount of refactoring. Thank you for your comment! – Wonka Jan 16 '19 at 03:45
  • @ljubadr I was able to leave the `api.php` file as is and not move it to `web.php` using ceejayoz awesome technique. Just had to update 'auth:api' to 'auth' and it worked. Thank you for your comment! – Wonka Jan 16 '19 at 03:47
  • I'm glad you found a solution! – ljubadr Jan 16 '19 at 03:55
  • @ljubadr I thought it worked, but it actually didn't, I added an update with the issue. I then tried your method of moving the routes to web and using the prefix/auth middleware combo, but still arrived at the same 2 issues in my update. Any idea how to proceed? – Wonka Jan 17 '19 at 19:51
  • Not sure if you also need `web` middleware. Sessions are created even if user is not logged in. – ljubadr Jan 17 '19 at 19:56
  • I tried adding it but it made no difference. I think it gets applied by default to web routes. I saw an article here for laravel 5.3 https://josephsilber.com/posts/2016/07/10/authentication-improvements-in-laravel-5-3#authentication-in-laravel-52 point 1, not sure if this still applies to this version of laravel, since the ajax requests work up until we refresh the page, then it becomes unauthenticated for any fetching of protected routes only. Pinging @joseph-silber as it's his article and he may know more... – Wonka Jan 17 '19 at 20:32
  • you are correct, `web` middleware is applied to web.php routes. Maybe you can try to debug the session, check [this question](https://stackoverflow.com/questions/42769388/laravel-session-always-changes-every-refresh-request-in-laravel-5-4). If you are calling `index.blade.php`, maybe print out the user email on the page, and see if it persists after page reload and that session is not lost. Are all ajax calls (GET, POST, ...) returning 401 after you refresh the page? – ljubadr Jan 18 '19 at 04:18
  • Yes 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 blade it shows `no auth email` before and after login, on page refresh, 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 ofcoure the persistent 401 for ajax calls to auth protected routes, non protected routes always load data perfectly via ajax. – Wonka Jan 18 '19 at 16:40
  • @ljubadr Good news! I figured it out, based on your linked question, but not those variables. 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'd love to award you the bounty since you helped me a lot with this and pointed me in the right direction. I'll do so as soon as you post an answer regarding checking and changing the `config/session.php` variable. I really appreciate it, thank you so much! – Wonka Jan 18 '19 at 20:22
  • Hey, I'm glad you figured it out. I just checked one of my projects, and `same_site => null`. So I guess you changed it at some point :) I'll add the answer soon – ljubadr Jan 18 '19 at 20:37
  • Because you are serving your `app.js` from your blade in production (no CORS), you can also look into [webpack dev server proxy](https://webpack.js.org/configuration/dev-server/#devserver-proxy). From the link __Proxying some URLs can be useful when you have a separate API backend development server and you want to send API requests on the same domain.__ This way you can remove CORS (if you have any code for that) – ljubadr Jan 18 '19 at 21:03

2 Answers2

2

Looks like your session was not persistent.

Check if you changed any values in config/session.php that might create problems.

You can check default sesion config values here

From the comments, @Wonka solved his problem by changing

'same_site' => 'strict'

to

'same_site' => null
ljubadr
  • 2,174
  • 1
  • 20
  • 24
  • 1
    In Laravel 7, `'same_site' => 'lax',` is the default option because "we will set this value to "lax" since this is a secure default value." (this works for me using Sanctum, same domain SPA Laravel + Vue) – agm1984 Jun 08 '20 at 03:13
1

It's doable (and I've done the same myself for some apps).

By default, the routes in routes/api.php don't have sessions available, but you can add the various Session/Cookie middlewares in app/Http/Kernel.php (stuff like \Illuminate\Session\Middleware\StartSession::class) to the API routes.

You can, as @ljubadr suggested, also put the API routes right in routes/web.php instead, although that'd probably mean you'd need to make other changes (like removing CSRF protection from the web routes).

ceejayoz
  • 176,543
  • 40
  • 303
  • 368
  • So I ran into 2 issues that you may have come across in your apps using this approach. I just added an update to the question; any idea how to resolve it? – Wonka Jan 16 '19 at 18:40
  • Hey sorry, still can't get the authentication session to work properly after integrating your solution as shown in my update. Can you take a look? May be something simple for you... – Wonka Jan 17 '19 at 15:20
  • Why would CSRF be an issue as the api request if sent via rightly configured axios will send `X-CSRF_TOKEN` in the header? – Hari Harker Sep 28 '20 at 21:23
  • @HariHarker A proper RESTful API is stateless. If you want to have a stateful API, that's fine; that's the "probably" bit. – ceejayoz Sep 28 '20 at 21:51