Invalid state using PHP SDK in Laravel app with custom user logic

Hey folks,
I’m back banging my head trying to get auth0 to play nicely with my Laravel app. All I want to use auth0 for is the login. User management is happening entirely within the app, so on login I want to check what comes back from auth0.

I’m not using the Laravel specific package for that reason. I want more granular control so I’m using the PHP SDK.

I have my 2 routes defined like so:

Route::get('login', function () {
    $auth0 = new Auth0([
        'domain' => config('services.auth0.domain'),
        'clientId' => config('services.auth0.client_id'),
        'clientSecret' => config('services.auth0.client_secret'),
        'cookieSecret' => 'c1XhulxTocmsvHIQ1tBOp7McazNhkZV7',
        'redirectUri' => config('services.auth0.redirect')
    ]);
    $auth0->clear();
    header("Location: " . $auth0->login(config('services.auth0.redirect')));
})->name('login');

that does what it needs to and redirects to universal login.

UL then correctly redirects to https://wahfires.test/login/auth0/callback?code=xxx&state=xxx and https://wahfires.test/login/auth0/callback is set as the callback URL for the application in the dashboard)

That URL runs executes this logic

Route::get('login/auth0/callback', function () {
    $auth0 = new Auth0([
        'domain' => config('services.auth0.domain'),
        'clientId' => config('services.auth0.client_id'),
        'clientSecret' => config('services.auth0.client_secret'),
        'cookieSecret' => 'c1XhulxTocmsvHIQ1tBOp7McazNhkZV7',
        'redirectUri' => config('services.auth0.redirect')
    ]);

    $auth0->exchange(config('services.auth0.redirect'), request('code'));
    header("Location: " . \route('home'));
});

but this results in an invalid state exception. I’ve cleared cookies etc but with no joy. Mightily frustrated.

Can anyone help?

Hey there!

@evansims would you be able to help us with it once you have some time? Sorry for constantly pinging you on PHP / Wordpress / Laravel related topics but you’re our guru for those topics :pray::nerd_face:

Thanks @konrad.sopala i got this one sorted.

1 Like

Would you be able to share it with the rest of community for the benefits of others? Thank you!

Sure.

So a little caveat for folks. I’m only using Auth0 here for authentication via Universal Login. We’re not using anything for permissions as we’re handling this via Laravel Permissions.

I have architected the app routes like so

<?php

use App\Http\Controllers\Auth\CallbackController;
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\LogoutController;
use Illuminate\Support\Facades\Route;

Route::get('login', LoginController::class)->name('login')->middleware('guest');
Route::get('callback', CallbackController::class)->name('callback');
Route::get('logout', LogoutController::class)->name('logout')->middleware('auth');

I created an Auth0ServiceProvider that looks like

<?php

namespace App\Providers;

use Auth0\SDK\Auth0;
use Illuminate\Support\ServiceProvider;

class Auth0ServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        $this->app->singleton(Auth0::class, function () {
            return new Auth0([
                'domain' => config('services.auth0.base_url'),
                'clientId' => config('services.auth0.client_id'),
                'clientSecret' => config('services.auth0.client_secret'),
                'cookieSecret' => config('services.auth0.cookie_secret'),
                'redirectUri' => config('services.auth0.redirect'),
            ]);
        });
    }

    /**
     * Bootstrap services.
     */
    public function boot(): void
    {
        //
    }

    public function provides(): array
    {
        return [Auth0::class];
    }
}

In LoginController

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Auth0\SDK\Auth0 as Auth0SDK;
use Illuminate\Http\Request;

class LoginController extends Controller
{
    protected Auth0SDK $auth0;

    public function __construct(
        Auth0SDK $auth0
    ) {
        $this->auth0 = $auth0;
    }

    public function __invoke(Request $request)
    {
        $this->auth0->clear();

        return redirect($this->auth0->login());
    }
}

in CallbackController

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Auth0\SDK\Auth0 as Auth0SDK;
use Illuminate\Http\Request;
use Str;

class CallbackController extends Controller
{
    protected Auth0SDK $auth0;

    public function __construct(
        Auth0SDK $auth0
    ) {

        $this->auth0 = $auth0;
    }

    public function __invoke(Request $request)
    {

        $response = $this->auth0->exchange();

        if (! $response) {
            abort(401, 'Unauthorized');
        }

        $auth0User = $this->auth0->getUser();

        $user = User::firstOrCreate(['email' => $auth0User['email']],
            [
                'given_name' => $auth0User['given_name'],
                'family_name' => $auth0User['family_name'],
                'password' => bcrypt(Str::random(32)),
                'email' => $auth0User['email'],
                'provider_id' => $auth0User['sub'],
            ]);

        auth()->login($user);

        $redirectUrl = session()->pull('currentUrl');

        return redirect()->to($redirectUrl ? $redirectUrl : '/');
    }
}

and in LogoutController

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Auth0\SDK\Auth0 as Auth0SDK;
use Illuminate\Http\Request;

class LogoutController extends Controller
{
    protected Auth0SDK $auth0;

    public function __construct(
        Auth0SDK $auth0
    ) {
        $this->auth0 = $auth0;
    }

    public function __invoke(Request $request)
    {
        $this->auth0->logout();

        auth()->logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        return redirect('/');
    }
}

2 Likes

Thanks a lot @1stevengrant !

1 Like

@evansims perhaps you might be able to shed some light on this for me.

The approach I’ve detailed above works for me locally. Does everything I need.

However, I’ve just deployed it to our dev environment using a different Auth0 app.

Login works but Logout never seems to clear the Auth0 browser session, so when I go to login again after having logged out, I’m never directed to Universal Login, it just obviously remembers the session and I’m straight back in.

The settings for both apps are the same, other than the Application URIs.

I’m so close to having things work as expected.

Hi @1stevengrant :wave:

To logout fully you need to redirect the user to Auth0’s logout endpoint. The SDK’s logout() method returns the URL that you need to issue the redirect to.

Based on your example, I’d suggest a change along these lines:

public function __invoke(Request $request)
{
    auth()->logout();

    $request->session()->invalidate();

    $request->session()->regenerateToken();

    $route = (string) url('/');
    $url = $this->auth0->logout($route);

    return redirect()->away($url);
}

This will briefly redirect the end user to the Auth0 logout endpoint and then back to the application’s index route.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.