Handling Laravel callback() exceptions: "Invalid state" and "Can't initialize a new session while there is one activ..."

When doing local development of Laravel apps using Auth0, we are surprised how often the application gets into a state where the callback route throws “Invalid state” exceptions or “Can’t initialize a new session while there is one active session already” exceptions occur. During local development this a little bit annoying, but when it happens on production sites it is problematic (since users are left with a nondescript “500 Server Error”).

  • The “Can’t initialize a new session while there is one active session already” exceptions occur if a user tries to log in when they’re already logged-in. Throwing an exception is not really helpful - in most cases it can simply be ignored.

  • The “Invalid state” exceptions can be caused by a few things, but most often seem to be when cookies/sessions get out of whack. We’ve found this can often be resolved by visiting the http://myapp/logout URL, although sometimes the only solution is to clear browser cookies.

We’re currently implementing some exception handling to handle these exceptions more gracefully. Our code below is now resolving most situations… except those cases that can only be rectified by clearing browser cookies.

(Note: We considered 3x possible places to implement the exception-handling logic:

  1. In app/Exceptions/Handler.php
  2. Creating our own app/Http/Controllers/Auth/Auth0Controller.php controller based based on a modified version of vendor/auth0/login/src/controllers/Auth0Controller.php.
  3. Creating our own app/Http/Controllers/Auth/MyAuth0Controller.php as a descendent of the vendor/auth0/login/src/controllers/Auth0Controller.php class.

Option 3 gave the most readable code.

Here’s our current “app/Http/Controllers/Auth/MyAuth0Controller.php” code (which is called from web.php instead of the default Auth0Controller.php file:

<?php

namespace App\Http\Controllers\Auth;

use Auth0\SDK\Exception\CoreException;
use Auth0\Login\Auth0Controller;

class MyAuth0Controller extends Auth0Controller
{
    /**
     * Wrap auth0 callback controller and handle exceptions if required
     */
    public function callback()
    {
        try {
            // try calling auth0 parent method
            return parent::callback();

        } catch (CoreException $exception) {
            $message = $exception->getMessage();

            // if active session exists, then user is already logged-in - no need to throw an error
            if ($message == "Can't initialize a new session while there is one active session already")
                return \Redirect::intended('/');

            // invalid state is often a temporary cookie or a timing problem, and is often resolved by simply logging out
            if ($message == "Invalid state")
                return \Redirect::route('logout');

            }
    }
}

Our current code leaves two unresolved questions that I’m hoping one of the Auth0 devs or other community members may be able to answer:

  1. Can anyone suggest a way to reset the necessary browser cookies for the (surprisingly-frequent) situations that they get out-of-whack? I’ve been contemplating a brute-force approach, but there may already be an Auth0 or Laravel method to do this cleanly. (Our current code is not ideal, because if the logout redirection doesn’t fix the problem, the user will repeatedly try to login (with no errors) only to find that they’re not logged-in again. This is not ideal.)

  2. Shouldn’t the default Auth0Controller.php be doing some of this exception-handling?

Context:

  • Which SDK does this apply to? (laravel-auth0)
  • Which verison of the SDK you are using? (6.0.1)
  • Which version of the platform are you facing this error on? (Laravel 7)
  • Was this code working before? (n/a)
  • Please capture and attach the stacktrace, it helps a lot! (n/a - various scenarios)
  • Please share the code that is causing the error. (see below)
  • Can you share a minimum reproducible? (not readily, but these exceptions are seen across quite a few of our projects and looking at the laravel-auth0 controller implementation I imagine we’re not alone)
  • Please capture and attach a HAR from the browser if this is an Authentication Bug / Issue. (n/a)
  • If this is a Management API Client bug, some libraries allow you to capture a HAR. (n/a)

CODE CAUSING THE ERROR:

These exceptions are raised during the call in Auth0Controller.php line 34 (which implements no exception-handling):

        ...
        // Try to get the user information
        $profile = $service->getUser();
        ...

The actual exceptions are raised by Auth0.php:

        ...
        if (! $state || ! $this->transientHandler->verify(self::TRANSIENT_STATE_KEY, $state)) {
            throw new CoreException('Invalid state');
        }

        if ($this->user) {
            throw new CoreException('Can\'t initialize a new session while there is one active session already');
        }
       ...

Note: This forum post helped to point us to the appropriate area of code: Invalid state (error 500) after PHP/SDK redirect - #4 by josh.cunningham

3 Likes

Just confirming we’ve had these same issues and your exception handling improves the user experience hugely. In answer to question 2, yes the default controller should be doing the exception handling. Perhaps the SDK devs didn’t spend enough time around testing this.

Perhaps in the code we can manually unset the offending cookies for the domain as you suggested?

    if (isset($_SERVER['HTTP_COOKIE'])) {
    $cookies = explode(';', $_SERVER['HTTP_COOKIE']);
    foreach($cookies as $cookie) {
        $parts = explode('=', $cookie);
        $name = trim($parts[0]);
        setcookie($name, '', time()-1000);
        setcookie($name, '', time()-1000, '/');
    }
}
1 Like

Not a Laravel master myself but thanks for the fruitful discussion on that front!

Anyone find a good solution to this? I’m running into the Core Exception: Invalid State quite often myself. I can’t for the life of me track down exactly whats happening or why.

I found that when users try to hit the login endpoint when they’re already logged in, it throws a Invalid State error, so checking before redirecting to Auth0 fixes that. I chalk it up to users bookmarking the login endpoint and using the bookmark to get to the app.

    public function login()
    {
        if ( Auth::check() ) {
            return redirect()->route('home');
        }

        $authorize_params = [
            'scope' => 'openid profile email',
        ];

        return \App::make('auth0')->login(null, null, $authorize_params);
    }

That fixed a number of the Invalid States, but there’s still far to many.

Hi @stanley :wave: Invalid State errors occur when the underlying Auth0 PHP SDK cannot verify the transient ‘state’ cookie set on the device, which occurs when login() is called, after your user returns from authenticating with Auth0’s Universal Login. This comparison happens (or should be happening) during your app’s callback route.

You’re quite right about firing off login() twice potentially causing those, as you’re overwriting the transient state cookie on the device each time it’s fired. This could cause a mismatch. We recommend this check-on-login-route approach in our Quickstart example for that reason.

As far as why you’re seeing elevated cases of Invalid State errors within your app, we’ll need to dig into your application’s structure more. If you would, please create a new thread detailing how you have your app configured, your routing, etc. so we can troubleshoot that together. Thanks!

1 Like

Thanks for following up on that Evan!

1 Like

We recently created a composer package to unify Auth0 usage across a number of our internal Laravel projects. During this process we did a lot of debugging + documentation, and found that the endless loop of the error “Can’t initialize a new session while there is one active session already” can be “fixed” by the following code - which we executed in a debug session during login… but haven’t yet incorporated into our codebase because we haven’t had a chance to understand any potential side-effects of doing this.

    session()->forget('auth0\_\_user')

Note: if you’re trying to use Auth0’s Laravel Quickstart, then some of the diagrams + documentation we produced may be helpful when trying to get your head around it all.

See: https://github.com/faithfm/laravel-auth0-pattern for more info.

2 Likes

Thanks for sharing that!