Logout issue with Laravel JWT-auth authentication

You can simply destroy the session on the client side when they logout, and "invalidate" the token on the backend (which's just another term for "blacklist", at least in JWT).

Technically destroying the token on the client side will be enough, but for session hijacking, invalidating it on the backend is a good idea too.

If you are invalidating, you'll need to destroy/ forget the frontend's token after you get your response from Laravel.

Backend

// Maybe set below to `false`,
// else cache may take too much storage.
$forever = true;

// Both loads and blacklists
// (the token, if it's set, else may raise exception).
JWTAuth::parseToken()->invalidate( $forever );

OR

JWTAuth::getToken(); // Ensures token is already loaded.
JWTAuth::invalidate($forever);

OR

$token = \JWTAuth::parseToken();
\JWTAuth::manager()->invalidate(
    new \Tymon\JWTAuth\Token($token->token),
    $forever
);

Note that Laravel's cache-driver should not be array, as blacklist is stored as cache.

See stackoverflow.com/Which cache driver to use?

Frontend

Then on angular side:

function logout()
{ 
    UserService.logout().$promise.then(function() {
        $cookieStore.remove('userToken');
        // redirect or whatever 
    });
}

Handle JWT exceptions (backend)

One way you can handle JWT exceptions is to setup an EventServiceProvider in laravel, here is what mine looks like:

use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider {

    /**
     * The event handler mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        'tymon.jwt.valid' => [
            'App\Events\JWTEvents@valid',
        ],
        'tymon.jwt.user_not_found' => [
            'App\Events\JWTEvents@notFound'
        ],
        'tymon.jwt.invalid' => [
            'App\Events\JWTEvents@invalid'  
        ],
        'tymon.jwt.expired' => [
            'App\Events\JWTEvents@expired'  
        ],
        'tymon.jwt.absent' => [
            'App\Events\JWTEvents@missing'
        ]
    ];

    /**
     * Register any other events for your application.
     *
     * @param  \Illuminate\Contracts\Events\Dispatcher  $events
     * @return void
     */
    public function boot(DispatcherContract $events)
    {
        parent::boot($events);

        //
    }
}

You'll register that in your app.php.

Then I implement the JWTEvents class with methods for each event.

class JWTEvents extends Event {
    
    // Other methods        

    public function invalid()
    {
        return response()->json(['error' => 'Token Invalid'], 401);
        die();
    }
}

Important thing to note is that we are catching the JWT exceptions and returning a json response with a specific status code.

On the angular side, I have in my httpInterceptor class, catches for these http status codes.

angular.module('ngApp')
    .factory('httpInterceptor', function($q, $log, $cookieStore, $rootScope, Response) {
        return {
            
            request: function(config) {
                // Where you add the token to each request
            },
            
            responseError: function(response) {

                // Check if response code is 401 (or whatever)
                if (response.status === 401) {
                    // Do something to log user out & redirect.
                    $rootScope.$broadcast('invalid.token');
                }
            }
        }
    });

This works for me.

public function logout( Request $request ) {

        // No need to get token, as "parseToken()" does that itself.
        //$token = $request->header( 'Authorization' );

        try {
            // Adds token to blacklist.
            $forever = true;
            JWTAuth::parseToken()->invalidate( $forever );

            return response()->json( [
                'error'   => false,
                'message' => trans( 'auth.logged_out' )
            ] );
        } catch ( TokenExpiredException $exception ) {
            return response()->json( [
                'error'   => true,
                'message' => trans( 'auth.token.expired' )

            ], 401 );
        } catch ( TokenInvalidException $exception ) {
            return response()->json( [
                'error'   => true,
                'message' => trans( 'auth.token.invalid' )
            ], 401 );

        } catch ( JWTException $exception ) {
            return response()->json( [
                'error'   => true,
                'message' => trans( 'auth.token.missing' )
            ], 500 );
        }
    }

As far as I understand, one thing that nobody stressed is the 'jwt.refresh' (aka RefreshTokenMiddleware) used to refresh the token.

Now, if anyone who wants to perform a logout action wraps the controller method in a route like

Route::group(['middleware' => ['jwt.auth', 'jwt.refresh']], function()...

for sure will get a new token in logout response hence the client will be able to perform new requests.

Hope this can help clarify this issue.