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

kubaszymanowski's avatar

Handling session expiry in a Vue SPA with Laravel backend.

I need to handle situations when user session expires on the front-end. I'll describe how I do this now, please tell me if you have a better solution.

When session expires Laravel by default returns a 500 response representing a TokenMismatchException. I decided to overwrite the handler to return a 420 response(or whatever arbitrary code, not to conflict with other returned by the app) and in the response body a new CSRF token created with session()->token(). Here's the handle method of the VerifyCsrfToken middleware:

public function handle($request, Closure $next)
{
    try {
        return parent::handle($request, $next);
    } catch(TokenMismatchException $exception) {
        return new Response([
            'newToken' => session()->token()
        ], 420);
    }
}

It's all great now. I make a POST request, it returns 420, I know I need to reauthenticate the user so this is what I did: I created an axios interceptor to catch these errors:

window.axios.interceptors.response.use(response => {
    // Everything fine, just pass it for further processing.
    return response;
}, error => {
    // If it is an HTTP error
    if(error.response) {
        if(error.response.status == 420) { // Session expired.

            // Set the new token as default HTTP header.
            axios.defaults.headers.common['X-CSRF-TOKEN'] = error.response.data.newToken;

            // If the user was signed in, we need to prompt for password.
            if(app.signedIn) {
                return new Promise((resolve, reject) => {

                    // Request the password from user.
                    app.requestPassword()
                       .then(() => {
                           // If the user succeeds with authentication
                           // Repeat the same request with the new token and return the response to the original requesting function.

                           let request = error.response.config;
                           request.headers['X-CSRF-TOKEN'] = error.response.data.newToken;

                           axios.request(request).then(response => {
                               resolve(response)
                           }, error => {
                               reject(error)
                           })
                        })
                });
            }
        } else {
            // Handle other errors
        }
    }

    return Promise.reject(error);
});

The app.requestPassword() looks like this:

requestPassword() {
    // Necessary to show the reauthentication modal.
    // It will be set back to false once the authentication succeeds.
    this.sessionExpired = true; 

    return new Promise((resolve, reject) => {
        app.$on('reauthenticated', (success) => {
            if(success) {
                resolve();
            } else {
                reject();
            }
        })
    })
}

Setting the sessionExpired to true shows a modal saying "Your session has expired. Please provide your password again". If the user provides correct password, the modal closes and the reauthenticated event is raised with success = true. If the user closes the modal manually without reauthenticating the same event is raised but with success = false and he gets redirected to login page.

Here's the typical request handling workflow: reauthenticate

It all looks and works great but I think it's more complicated then it should be. I end up creating 4 nested promises and I think there might be a better way to handle this.

Have you got any experience with solving this problem? I'd appreciate any hints, articles etc.

0 likes
2 replies
kubaszymanowski's avatar

It's quite a sizable codebase now, I need to be sure it's a good idea to switch to JWT.

Is it a lot of work to change the authentication method? What are the benefits of JWT? How does it make it any simpler to handle token expiry?

Is it good idea to use Laravel Passport or are there simpler solutions? I've read about some issues with AJAX authentication(https://github.com/laravel/passport/issues/52 et. al.)

1 like

Please or to participate in this conversation.