Laravel Sanctum | Delete current user token not working
Asked Answered
C

6

9

i really need help with one little thing I try to do. I try to use concurrent personal_access_tokens in my laravel / Vue setup for one user. Everything is working fine, all but one thing where I want to delete one token by it's id when the user is logging out. With the login of one user, I create a personal_access_token for them. With the logout this specific access_token should be deleted.

Right now in my logout method, I delete all tokens. That works fine, but when deleting one specific token (which should work) I always get errors that this method doesn't exist:

public function logout(Request $request){
    
   Auth::guard('web')->logout();
    
   // First try: auth()->user()->currentAccessToken()->delete();
   // Second try: $request->user()->token()->revoke();
   auth()->user()->tokens()->delete();
}

The error:

LOG.error: Call to undefined method Laravel\Sanctum\TransientToken::delete() {"userId":18,"exception":{}}

api.php

Route::group(['middleware' => 'auth:sanctum'], function () {
    Route::post('/logout', [AuthController::class, 'logout']);
});

I tried following things from:

https://laracasts.com/discuss/channels/laravel/passport-how-can-i-manually-revoke-access-token (passport) https://laracasts.com/discuss/channels/laravel/deleting-users-passport-token-on-logout https://divinglaravel.com/authentication-and-laravel-airlock https://laracasts.com/discuss/channels/laravel/spa-and-mobile-logout?page=1&replyId=698040

In all those threads the used methods should work but not for me. Do I overlook something?

I appreciate every help!

Chinookan answered 5/7, 2021 at 11:15 Comment(2)
did you try $request->user()->currentAccessToken()->delete();?Sacaton
Yes I indeed tried everything from the docs of Sanctum.Chinookan
L
19

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;
});
Luellaluelle answered 6/10, 2022 at 22:31 Comment(4)
Thanks... That's totally correct! I've spent 8 hours to dive deep to Sanctum + Auth source to understand how Sanctum works in laravel. Your comment should be an accepted answer.Groth
My problem is currentAccessToken() is empty for me?!Katusha
I have the same error on Personal Access Token which really baffles me as its a Model instance (there is no web guard in my app).Kosse
This comment describe the athentication process better than the documentations does. Thank you.Craniate
T
7

I have the same problem, calling auth()->user()->currentAccessToken()->delete() gives the error Call to undefined method Laravel\\Sanctum\\TransientToken::delete().

I figured out that this is because of how the Sanctum authentication guard works. When a Laravel session is already in place, via a session cookie, the guard puts a TransientToken on the user, which is then returned via currentAccessToken(). However this TransientToken is not the real PersonalAccessToken and doesn't have the delete() method.

Found this out in the source code here.

My way around is to put the logout route not in the web group but instead in the api group, which doesn't include the middleware for the sessions.

Tokyo answered 8/8, 2021 at 22:54 Comment(1)
I have the exactlly same problem. My logout route is api/v1/auth/logout and using the API guard group... Unfortunately this doesn't fix the problem for me. Can you please share your steps?Lenrow
L
4

I faced the same problem, It seems like Laravel Sanctum uses TransientToken as default Token class instead of PersonalAccessToken if the user is logged in via session/cookie. And the TransientToken only has can/cant methods, so it doesn't support delete() or ->id property.

so what I did is checking if currentAccessToken object has delete method, if so I use it, if not then I go further and delete the session.

if(method_exists(auth()->user()->currentAccessToken(), 'delete')) {
    auth()->user()->currentAccessToken()->delete();
}

auth()->guard('web')->logout();
Lacrimatory answered 12/12, 2021 at 10:51 Comment(0)
U
2

If you use a bearer token for the logout this code will help. Sanctum Bearer token starts with token ID from the personal_access_tokens table

$tokenId = Str::before(request()->bearerToken(), '|');
auth()->user()->tokens()->where('id', $tokenId )->delete();
Ubiety answered 10/12, 2021 at 19:41 Comment(0)
H
1

For a specific user:

Auth::user()->tokens()->where('id', $id)->delete();

For requested user who want to logout

$user = request()->user();
$user->tokens()->where('id', $user->currentAccessToken()->id)->delete();
Homoio answered 5/7, 2021 at 11:31 Comment(2)
$user->currentAccessToken()->id is not working. The method is undefined.Chinookan
Try to debug first check dd($user->currentAccessToken()) if it gives some value?Homoio
T
1

It's pretty clear on the documentation: https://laravel.com/docs/8.x/sanctum#revoking-tokens

$user->tokens()->where('id', auth()->id())->delete();
Toxicogenic answered 5/7, 2021 at 12:35 Comment(3)
I tried to make concurrent personal_access_tokens of one user. If I take the id of the authenticated user, I delete every token. When I access the currentAccessToken, I can't get the id and I can't delete it.Chinookan
@Chinookan I just tested it on a project of mine where I am using Sanctum and it works completely fine. Here's a screenshot. It returns true when i dd. prntscr.com/18rgpmnToxicogenic
To get the authenticated user token you do: auth()->user()->currentAccessToken()->token;Toxicogenic

© 2022 - 2025 — McMap. All rights reserved.