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

Barley's avatar

Sanctum SPA Authentication Guard::attempt error cause?

Hi all,

I'm trying to get a very simple test project up and running. The goal is to get Sanctum running, without using Fortify and / or Jetsream / front-end scaffolding. So I made a very simple blade template containing a login form which POSTs to an AuthController and calls a login method. This method uses Auth::guard('sanctum')->attempt($validatedInput) to validate and login and retreive the corresponding user. I installed Sanctum, published it's settings, added the login (web middleware) route, but when trying to login I get the following error:

Method Illuminate\Auth\RequestGuard::attempt does not exist.

I'm using Auth::guard('sanctum') here, when using the web guard it seems to work fine. What am I doing wrong here?

You can find my project at:

https://github.com/jpostema81/sanctumtest

I hope to get some directions as I find it (very) hard to find anything useful in the official documentation...

0 likes
9 replies
Barley's avatar

Hi, no replies yet... I'm wondering if there are people who understand Santum SPA authentication at all? I guess I have to use Auth::guard('web')->attempt() to login using Sanctum authentication?

I found another contradicting (in my opinion) thing about API routes and statefulness:

"The routes in routes/api.php are stateless and are assigned the api middleware group." (https://laravel.com/docs/8.x/routing#the-default-route-files)

"This guard will ensure that incoming requests are authenticated as either a stateful authenticated requests from your SPA or contain a valid API token header if the request is from a third party" (https://laravel.com/docs/8.x/sanctum#protecting-routes)

To me both sentences are contradictory as the first sentence tells that all API routes in api.php are stateless whereas the second sentence tells that API routes that are protected by the Sanctum guard are stateful. As Sanctum uses a session, this would be stateful.

Can someone explain me how to interpretet these things which in my opinion are contradictory?

rodrigo.pedra's avatar

Auth::attempt is only available on guards that implement the StatefulGuard trait.

The only guard Laravel ships that implements that is the SessionGuard which is available through the web middleware stack (stateful).

The idea of Sanctum is to leverage session cookies defined by a stateful login system (as the one using regular web middleare and SessionGuard) to make it easier to consume an API without the hassle of using a full stateless flow such as using Laravel Passport.

In Sanctum's docs it is clear you need to add a middleware to check session cookies in your API routes:

https://laravel.com/docs/8.x/sanctum#sanctum-middleware

This middleware is responsible for ensuring that incoming requests from your SPA can authenticate using Laravel's session cookies

Also its docs states you should use your traditional login system:

Once CSRF protection has been initialized, you should make a POST request to the typical Laravel /login route. This /login route may be provided by the laravel/jetstream authentication scaffolding package.

Reference: https://laravel.com/docs/8.x/sanctum#spa-authenticating

Two important things here:

1 - You need to use traditional session/cookie-based login. (implied here "...you should make a POST request to the typical Laravel /login route.) 2- You don't need to use Fortify/JetStream for that. Note here: "... This /login route may be provided by the laravel/jetstream ... docs states the login route may be provided, not that is should be provided.

Bottom line:

1 - Username/Password login should be done using traditional cookie-based login.

If you don't want to use Fortify/Jetstream you can still use laravel/ui, it is still support and fits your needs, as per this package readme:

This project is a very simple authentication scaffolding ...

https://github.com/laravel/ui/blob/3.x/README.md

If you still don't want to use that, you can implement the login using SessionGuard following the docs instructions here:

https://laravel.com/docs/8.x/authentication#authenticating-users

2 - Use Sanctum afterwards to easily consume your API from your SPA in the same domain relying on cookies set by your session based authentication.

Don't forget to refresh the CSRF token on your SPA as outlined here:

https://laravel.com/docs/8.x/sanctum#spa-authenticating

3 - If you need third-party applications to consume your API, then you can issue tokens for those.

Sanctum first-party SPAs are meant to use leverage cookies for easy API consumption.

Hope it helps.

1 like
Barley's avatar

Hi Rodrigo,

thanks for your extensive reply! It get's a little more clear now. But I still have some things which I find diffucult to understand:

"2 - Use Sanctum afterwards to easily consume your API from your SPA in the same domain relying on cookies set by your session based authentication."

I always had the idea that API routes were exclusively for stateless (token based) authentication, as, as far as I remember, not for anything that had to do with session - this is what Laravel documentation always implied (at least to me). Thit confuses me.

I think I start to understand the idea that Sanctum enables API routes to use either stateful (first-party) and stateless (third-party) authentication. So by adding Sanctum middleware to the API middleware group in app/Http/Kernel.php the API routes get added functionality (session / stateful authentication / route protection). Is this a correct interpretation?

(at first I was wondering why add extra middleware to the API routes and not use web routes instead, but if I'm correct this is in order to get 1 API routing which is able to serve both first - and third party requests?

Regards

rodrigo.pedra's avatar
Level 56

Kind of.

Actually the middleware checks if there is a cookie header and then:

If there is a cookie

  • Sanctum tries to decrypt it and tries to extract the authentication info from those cookies
  • If succeeded it uses that info to authenticate the request and find the user
  • No session is started, just decrypted and checked, so you won't have access to a session object on those routes
  • No cookies are overwritten on response
  • So, from your SPA you don't need to store/send authentication tokens after logging the user in, which makes life easier

If there is no cookie (for example from a third-party client)

  • Sanctum tries to extract the authentication token from the Authorization header as a Bearer token
  • If token is found it uses that to authenticate the request and find the user
  • No session is involved, no cookies are parsed or sent back

Bottom line is to combine ease of session-based login and diminish the work needed to exchange tokens on your first party SPAs.

Hope it makes more clear.

1 like
Barley's avatar

Hi Rodrigo,

I replied a bit too soon... Hope you can answer another related question:

So after logging in with session based authentication, cookies are set. If I'm correct, this is a persistent cookie which has a name "laravel_session" and has a value (a hashed string).

Is this cookie is used for extraction of authentication information in order for Sanctum middleware to check if a request is permitted - without actually starting a session? (I assume technically a session can be started as the web guard login starts a session) If so, how does this work? Is the content of the cookie a session-id, what I would expect from logging in through the default web guard which starts a session? Or is it a hash which can be decrypted as stated in your reply and which contains user information?

I hope my question makes sense...

rodrigo.pedra's avatar

It is a session-id.

From laravel docs:

Sanctum uses Laravel's built-in cookie based session authentication services.

Reference: https://laravel.com/docs/8.x/sanctum#how-it-works-spa-authentication

But as it runs in the same app, once it extracts a session-id from the cookies, it can retrieve the session contents, including the user id.

You can see here:

https://github.com/laravel/sanctum/blob/1c7ce3f04974847c31a92655d652523d14812788/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php#L18-L34

That it starts the session, so all the session contents are available to the request.

And then, when the guard runs it checks for a regular session-guard user here:

https://github.com/laravel/sanctum/blob/1c7ce3f04974847c31a92655d652523d14812788/src/Guard.php#L54-L58

Barley's avatar

Hi Rodrigo, thanks again for your reply! The only thing I don't understand is this:

"No session is started, just decrypted and checked, so you won't have access to a session object on those route"

So there is no session support if I'm correct, so you can't save data to the session while doing a sanctum authenticated API request. But:

"That it starts the session, so all the session contents are available to the request."

...so a session is started? And it is read (user id). But there is no session write support?

I hope my question makes sense!

rodrigo.pedra's avatar

But there is no session write support?

No. I was wrong. Sorry.

Just installed a fresh Laravel app, added Sanctum, configured some api and regular routes and played with sessions.

If a request is made by your first-party frontend, in other words if EnsureFrontendRequestsAreStateful's fromFrontend(...) method returns true, the session is set on the request and any changes to session are written back.

It was a misunderstanding from my side as before I tested the API routes directly and not from a SPA, so fromFrontend(...) returned false and no session was available.

In summary: When calling from your SPA a session is started and there is write support. Just as a regular endpoint.

But if you plan on providing third-party access to your API routes I wouldn't rely on session support on those routes.

I apologize again, hope it is clearer now.

1 like

Please or to participate in this conversation.