A few notes about the implementation:
- Don't use
delete()
and logout()
in the same code;
- Never use the
web
guard on token-based requests;
- Don't use
attempt($credentials)
for token-based login;
In short, this exception is an indicator of mixed cookie and token authentication code.
Don't use delete()
and logout()
in the same code
Sanctum has two ways of authentication: cookie and token.
Each type of authentication require a totally different implementation. They are not compatible.
- Cookie logout:
auth('web')->logout()
;
- Token logout:
auth('sanctum')->user()->currentAccessToken()->delete()
;
When a route is executed, the Sanctum guard detects the type of authentication: cookie or token. If it is cookie, it returns a TransientToken
on currentAccessToken()
. If it is a token, it returns a PersonalAccessToken
.
Because this decision affects everything after, you cannot mix cookie and token code. You must create separate code for each type if you want to accept them both.
That means you put cookie authentication routes in web.php
and token authentication routes in api.php
.
If you mix them, you get a delete method not found
in a cookie-based logout, and a logout method not found
in a token-based logout.
Never use the web
guard on token-based requests
The web
guard is an alias for SessionGuard
. As the name implies, it is based in sessions and cookies.
Laravel automatically loads the sessions and cookies middlewares for the web.php
routes. That's why you can use the "web" guard for the web.php
routes.
Laravel doesn't load these middlewares for the api.php
routes. Because of that, we cannot use the web
guard in api.php
routes.
So, make sure you don't use the web
guard in any api.php
route.
For Sanctum, you can use the sanctum
guard instead.
Also, notice that web
is the default guard when not specified. To be safe, explicitly set the guard for every auth call:
Auth::guard('sanctum')->...
;
auth('sanctum')->...
;
auth()->guard('sanctum')->...
;
Don't use attempt($credentials)
for token-based login
Many people implement the Sanctum login with attempt($credentials)
. That's wrong for token-based authentication.
How it should be:
- Cookie login:
auth('web')->attempt($credentials)
;
- Token login: manual (i.e. fetch the user + check password + return a token);
If you use auth()->attempt($credentials)
you use the "web" guard. The web guard uses cookies, which doesn't work in api.php
routes and is not meant for token-based authentication.
You may not get an error if you use it, and the authentication may even work, but it is wrong and the main reason you are getting an exception.
Unfortunately, there's no auth('sanctum')->attempt($credentials)
, so you have to implement it manually.
The official Sanctum documentation has a snippet with the implementation. You just have to copy and paste (and adjust if needed):
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
Route::post('/sanctum/token', function (Request $request) {
$request->validate([
'email' => 'required|email',
'password' => 'required',
'device_name' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
return $user->createToken($request->device_name)->plainTextToken;
});
$request->user()->currentAccessToken()->delete();
? – Sacaton