php - "logoutOtherDevices" isn't working with Laravel Sanctum

i want to use Auth::logoutOtherDevices($currentPassword) in laravel 8 to logout user from other devices after changing password. As per documentation ia have uncomment the line \Illuminate\Session\Middleware\AuthenticateSession::class,. But unfortunately, it's not working. It says Method Illuminate\Auth\RequestGuard::viaRemember does not exist. Can anyone please help me to sort it out how can i add logout from other devices functionality with the help of Auth::logoutOtherDevices($currentPassword) or any manual method ?

Here is my code:

$rules = [
        'currentpass' => 'required',
        'newpass'     => 'required|min:6',
        'confnewpass' => 'required|same:newpass|min:6'
    ];

    $messages = [
        'currentpass.required' => 'Please enter your current password.',
        'newpass.required'     => 'Please provide a new password.',
        'newpass.min'          => 'Password must contain minimum 6 characters.',
        'confnewpass.required' => 'Please provide your new password again to confirm.',
        'confnewpass.same'     => 'Both new passwords must be same.',
        'confnewpass.min'      => 'Password must contain minimum 6 characters.'
    ];

    $this->validate($request, $rules, $messages);

    $currentPass = $request->input('currentpass');
    $newPass     = $request->input('newpass');

    try {
        $user = User::findOrFail(Auth::id());
    } catch (ModelNotFoundException $ex) {
        $response['error'] = true;
        $response['errors']['notFound'] = ['User Not Found.'];

        return response()
            ->json($response, 400, [], JSON_PRETTY_PRINT);
    }

    if (!Hash::check($currentPass, $user->password)) {
        return Redirect::back()
            ->withErrors(['Current Password', 'Please provide your current password properly.']);
    }

    $isChar = preg_match('/[a-zA-Z]+/', $newPass);
    $isNum  = preg_match('/\d+/', $newPass);

    if (!($isChar && $isNum)) {
        $response['error'] = 'Password must contain minimum 6 characters with at-least one letter and one number.';

        return response()
            ->json($response, 200, [], JSON_PRETTY_PRINT);
    }

    /** hash password */
    $hashpass = Hash::make($newPass);

    $user->password = $hashpass;

    try {
        $user->save();
    } catch (QueryException $ex) {
        return Redirect::back()
            ->withErrors(['query', $ex->getMessage()]);
    }

    Auth::logoutOtherDevices($currentPass);

    return Redirect::back()
        ->with('success', 'Your password has been successfully updated.');

Answer

Solution:

This is because Laravel Sanctum is using its own middleware, namely Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful. While Auth::logoutOtherDevices($currentPassword) needs \Illuminate\Session\Middleware\AuthenticateSession to work.

To work around this, you can extend \Illuminate\Session\Middleware\AuthenticateSession to works with Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful. Here's the steps:

  1. Create a new file AuthenticateApiSession.php in app/Http/Middleware (or any other folder you like). The AuthenticateApiSession.php should looks like this:

    <?php
    
    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Session\Middleware\AuthenticateSession;
    
    class AuthenticateApiSession extends AuthenticateSession
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @return mixed
         */
        public function handle($request, Closure $next)
        {
            if (! $request->hasSession() || ! $request->user()) {
                return $next($request);
            }
    
            // Remove or comment this code block, or you'll get error.
            /*if ($this->auth->viaRemember()) {
                $passwordHash = explode('|', $request->cookies->get($this->auth->getRecallerName()))[2] ?? null;
    
                if (! $passwordHash || $passwordHash != $request->user()->getAuthPassword()) {
                    $this->logout($request);
                }
            }*/
    
            if (! $request->session()->has('password_hash')) {
                $this->storePasswordHashInSession($request);
            }
    
            if ($request->session()->get('password_hash') !== $request->user()->getAuthPassword()) {
                $this->logout($request);
            }
    
            return tap($next($request), function () use ($request) {
                $this->storePasswordHashInSession($request);
            });
        }
    }
    
  2. Add the newly created App\http\Middleware\AuthenticateApiSession class to api middleware group in app/Http/Kernel.php. Beware that it must be inserted after EnsureFrontendRequestsAreStateful.

     'api' => [
         EnsureFrontendRequestsAreStateful::class,
         \App\Http\Middleware\AuthenticateApiSession::class,
         'throttle:240,1',
         'auth:airlock', 
         'branch.default', 
         'bindings',
     ],
    

That's it, now every ajax request to guarded API endpoint from other device should returns 401 response after Auth::logoutOtherDevices($currentPassword) is called. You should set your SPA (You're using sanctum, so I assume you're building an SPA) to handle invalid session in the ajax request though. Maybe redirecting the user to login page using javascript when encountering 401 ajax response.

Source