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

BMS51's avatar
Level 1

Laravel Sanctum multi guard weird session issue (users sharing a session).

Hi,

I'm trying to make Fortify with sanctum work with a custom Guard called SPA.

The admins are logging in on the backend with the Web guard.

The users are logging in on a subdomain that will host a VUEJS SPA.

Admin logs in on -> https://test.local:8890/admin/login and is redirected to https://test.local:8890/admin/dashboard that works -> ok

Users log in on https://spa.test.local:8890 (vue app). -> ok works

Vuefile:

import axios from 'axios'
axios.defaults.withCredentials = true;
axios.defaults.baseURL = 'https://test.local:8890'

export default {
	name: 'Home',
	components: {
		HelloWorld
	},
	data: () => {
		return {
  			email : '[email protected]',
  			password : 'Password1!'
	}
},
methods : {
	login() {
  axios.get('/sanctum/csrf-cookie').then(response => {
    console.log(response.code);
    axios.post('web-api/login', {
      email : this.email,
      password : this.password
    }).then(response => {
          console.log(response);
      }
    ).catch(error => {
      console.log(error);
    })
  });
}}}

So the idea is that users can not log in on the dashboard and have a separate table

When i log in on my admin i go to the dashboard. I open a new tab in chrome and log in as the user on the subdomain in the vue app. When i go back to the dashboard i'm still logged in so good and i'm logged in on the other tab as the user (super)

But when inspecting the cookies in chrome https://test.local:8890/ and https://spa.test.local:8890/ share the same cookie laravel_session and xcsrf token... they are identical...

So now when i go to admins tab and go to the web-api route https://test.local:8890/web-api/user i see the user's credentials :

{"id":4,"first_name":"Happy","last_name":"Doe","email":"[email protected]"}

Yet i can still visit my dashboard as Admin.

config/auth.php

'defaults' => [
    'guard' => 'web',
    'passwords' => 'admins',
],

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'admins', //admin provider for web
    ],
    'spa' => [
        'driver' => 'session',
        'provider' => 'users' //user provicer for spa (and api)
    ],
    /*'api' => [
        'driver' => 'token',
        'provider' => 'users',
        'hash' => false, //user provider
    ],*/
],

I've added guard parameter to sanctum config.

config/sanctum:

'guard' => 'spa', //set sanctum guard to spa.

In the Kernel file i've copied everything from the 'web' middleware to 'spa' because it works almost the same.

Kernel.php:

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
    'spa' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class
    ],
    'api' => [
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

Configured Sanctum parameters in the env file.

.env :

SESSION_DOMAIN=test.local
SANCTUM_STATEFUL_DOMAINS=.test.local

Added the SPA to routeServiceProvider boot method

RouteServiceProvider.php:

 public function boot()
{
    $this->configureRateLimiting();

    $this->routes(function () {
        Route::prefix('web-api')
            ->middleware('spa')
            ->namespace($this->namespace)
            ->group(base_path('routes/spa.php'));

        /*Route::prefix('api')
            ->middleware('api')
            ->namespace($this->namespace)
            ->group(base_path('routes/api.php'));*/

        Route::middleware('web')
            ->namespace($this->namespace)
            ->group(base_path('routes/web.php'));
    });
}

Then my login function in SpaAuthController: (as used by the spa)

public function loginSpa(Request $request){

	//Regenerate session 

    $request->session()->regenerate();

    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
    ]);

    $authAttempt = Auth::guard('spa')->attempt(['email' => $request->email, 'password' => $request->password]);

    if($authAttempt){
        $user = Auth::guard('spa')->user();

        if (!$user->hasVerifiedEmail()){
            abort(401, 'Email address not verified.');
        }

        if ($user->blocked){
            abort(401, 'Your Account blocked.');
        }

        return response([],204);
    }

    return response(['message' => 'The provided credentials are incorrect.'],422);

}

The entire web login is handled by default fortify controllers...

POST | admin/login | | Laravel\Fortify\Http\Controllers\AuthenticatedSessionController@store | web

At this point it's getting kinda funny, what am i doing wrong here and should i be worried for potential security issue presume yes :D)

0 likes
1 reply
BMS51's avatar
BMS51
OP
Best Answer
Level 1

Don't try it this way, it will never work!

Sanctum will use the web guard by default. Since Multi auth is not possible in Fortify (unless with heavy customisation). I was focused on Laravel multi auth which is possible without fortify but NO in combination with fortify.

Please or to participate in this conversation.