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

Punctura's avatar

Laravel 8 Sanctum SPA logout with tests

Hi everyone! I've created a simple SPA using Sanctum. The doc says /docs/8.x/sanctum#spa-authentication:

For this feature, Sanctum does not use tokens of any kind. Instead, Sanctum uses Laravel's built-in cookie based session authentication services.

And also

You are free to write your own /login endpoint; however, you should ensure that it authenticates the user using the standard, session based authentication services that Laravel provides. Typically, this means using the web authentication guard.

So I'm using Auth::attempt($request->validated()) in my api/login endpoint and I see it creates session

Illuminate\Session\Store {#306
  #id: "nIiSSbsm2Weve9lPlzvbHKphyB55cjmSFICxugIm"
  #name: "laravel_session"
  #attributes: array:5 [
    "_token" => "KdTqsOL06RW5T7cDiIuMaPb4DKVWfFaAbqCFK9ZK"
    "_flash" => array:2 [
      "old" => []
      "new" => []
    ]
    "_previous" => array:1 [
      "url" => "http://laravel-artem-test.com/login"
    ]
    "login_web_59ba36addc2b2f9401580f014c7f58ea4e30989d" => 6
    "url" => array:1 [
      "intended" => "http://laravel-artem-test.com/home"
    ]
  ]
  #handler: Illuminate\Session\FileSessionHandler {#305
    #files: Illuminate\Filesystem\Filesystem {#163}
    #path: "/home/vagrant/code/laravel-test/storage/framework/sessions"
    #minutes: "120"
  }
  #started: true
}

In my api/logout endpoint I'm also using standard laravel logout and it works properly

Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();

$request->session() shows

Illuminate\Session\Store {#309
  #id: "qmzrqGplh9O8uT9chcKqbudIZ2fGvcU1WyJkSYch"
  #name: "laravel_session"
  #attributes: array:1 [
    "_token" => "FJcXqGlSE9k9SY1csWPjTVSS5OmBuFqFO1JHokRA"
  ]
  #handler: Illuminate\Session\FileSessionHandler {#308
    #files: Illuminate\Filesystem\Filesystem {#163}
    #path: "/home/vagrant/code/laravel-test/storage/framework/sessions"
    #minutes: "120"
  }
  #started: true
}

But I want to do feature test for logout endpoint and here I faced the issue. My test method:

public function test_logout()
    {
        $user = User::factory()->create();

        Sanctum::actingAs($user, [], 'web');

        $response = $this->postJson(route('api.logout'));

        $response->assertStatus(200)
            ->assertJsonCount(1)
            ->assertJson(fn (AssertableJson $json) =>
            $json->has('redirect')
                ->whereType('redirect', 'string')
            );

        $this->assertGuest();
    }

I'm getting

RuntimeException: Session store not set on request. in /home/vagrant/code/laravel-test/vendor/laravel/framework/src/Illuminate/Http/Request.php:515

and $response->ddSession(); gives me []

As I understand Sanctum::actingAs($user, [], 'web'); doesn't create session. But event if I use $this->actingAs($user, [], 'web')->withSession(['test' => true]) I still get [] and the same error occures.

I've already googled a lot but couldn't find any solution. Maybe someone has some thoughts? Thanks

0 likes
1 reply
Tahiaji's avatar

I'm not at all sure that this question is relevant. But maybe it will be useful to someone if they stumble upon it.

I use real login instead Sanctum::actingAs to use session and do not use tokens. (I put this method in my base TestCase class)

protected function loginWithSession(?User $user = null): User
    {
        if ($user === null) {
            $user = $this->createUser();
        }
        $this->postJson(
            '/auth/login',
            ['email' =>  $user->email, 'password' => UserFactory::DEFAULT_PASSWORD],
            ['referer' => parse_url((string) env('APP_URL'), PHP_URL_HOST)]//required for EnsureFrontendRequestsAreStateful to start session
        )->assertNoContent();

        return $user;
    }

And my test

    #[Test]
    public function testSuccess()
    {
        $this->assertGuest();
        //note: we use loginWithSession() as here we do not need token
        $user = $this->loginWithSession();
        $this->assertAuthenticatedAs($user);

        $this->postJson(
            $this->getUrl(),
            [],
            ['referer' => parse_url((string) env('APP_URL'), PHP_URL_HOST)] //set any domain from `sanctum.stateful` config. 
        )->assertNoContent();
        $this->assertGuest();
    }

Please or to participate in this conversation.