Invalid State Error after Upgrading from Auth0 PHP SDK v7 to v8

Hello Auth0 team,

I've encountered an issue after upgrading the Auth0 PHP SDK from version 7 to version 8. The error occurred within a WHMCS environment. Below are the details of the error I'm experiencing:

**Error Message:**

[Mon Jul 22 14:53:57.433504 2024] [php:notice] [pid 1758830] [client 192.168.81.227:61846] [WHMCS Application] ERROR: Auth0\SDK\Exception\StateException: Invalid state in /var/www/html/whmcs/modules/addons/auth0/vendor/auth0/auth0-php/src/Exception/StateException.php:82 Stack trace: #0 /var/www/html/whmcs/modules/addons/auth0/vendor/auth0/auth0-php/src/Auth0.php(187): Auth0\SDK\Exception\StateException::invalidState() #1 /var/www/html/whmcs/sso.php(22): Auth0\SDK\Auth0->exchange() #2 {main} {“exception”:“[object] (Auth0\SDK\Exception\StateException(code: 0): Invalid state at /var/www/html/whmcs/modules/addons/auth0/vendor/auth0/auth0-php/src/Exception/StateException.php:82)”}

**Steps to Reproduce:**
1. Upgraded from Auth0 PHP SDK v7 to v8.
2. Encountered the error during the login process using Auth0's 'exchange' method in the WHMCS environment.

**Expected Behavior:**
The expected behavior was that the login process would complete without errors, as it did in the previous version of the SDK.

**Actual Behavior:**
An "Invalid State" error is thrown during the authentication flow, preventing the successful completion of the login process.

**Environment:**
- Auth0 PHP SDK Version: 8.x.x
- PHP Version: 8.x.x
- WHMCS Version: 8.x.x


Can you provide any guidance on what might be causing this issue and how to resolve it?

Thank you in advance for your help.

Best regards,
Reddy

Hi @yrmallu :wave: An invalid state error occurs when the state cookies generated and given to the end user’s device (when login() is called) do not match what is found during the return trip back after authenticating with Auth0.

This usually occurs because login() is inadvertently being called multiple times in the host application. Can you check if that is the case on your end?

P.S. I have a v7 → v8 migration guide you might find useful as well, if you haven’t come across it before; auth0-PHP/UPGRADE.md at main · auth0/auth0-PHP · GitHub

Hi @evansims :raised_hand: Thanks for prompt response. Here is my code and didn’t have multiple login().

SSO working fine with Auth0 version 7.x but not with Auth0 version 8.x

$auth0->getUser() is giving invalid state error. Are there any changes on this method?

It was working fine with Auth0 version 7.x .

The following code snippet

<?php

session_start();

require(__DIR__ . '/init.php');
require_once("modules/addons/auth0/auth0.php");
use Auth0\SDK\Exception\StateException;
use Auth0\SDK\Utility\PKCE;
use Latte\Engine as Template;

global $auth0;

if (isset($_GET['code'])) {

    $userInfo = $auth0->getUser();

    if (!$userInfo) {
        die('Error retrieving user info from Auth0.');
    }

    // Search for the user in WHMCS
    $result = localAPI('GetClients', ['search' => $userInfo['email']]);

    if ($result['totalresults'] <= 0) {
        // User doesn't exist in WHMCS, create a new one
        $createClientParams = [
            'firstname' => $userInfo['name'],
            'lastname' => $userInfo['name'],
            'email' => $userInfo['email'],
        ];
        $result = localAPI('AddClient', $createClientParams);

        if ($result['result'] !== 'success') {
            die('Error creating WHMCS client: ' . $result['message']);
        }

        $clientId = $result['clientid'];
   } else {
        $clientId = $result['clients']['client'][0]['id'];
    }

    $ssoResult = localAPI('CreateSsoToken', ['client_id' => $clientId]);

    if ($ssoResult['result'] !== 'success') {
        die('Error creating SSO token: ' . $ssoResult['message']);
    }

   // Redirect the user to WHMCS with the SSO token
   $redirectUrl = $ssoResult['redirect_url'];
   header('Location: ' . $redirectUrl);
   exit;
}

if (null === $session) {
    header("Location: " . $auth0->login());
    exit;
}

if ($session->accessTokenExpired === true) {
    $auth0->renew();
    $session = $auth0->getCredentials();
}

Hi @yrmallu :wave:

Ah, it looks like you might just be missing the exchange() step. This was introduced as part of v8 — you’ll need to do that before getUser() will work.

You can find some example usage in the README: GitHub - auth0/auth0-PHP: PHP SDK for Auth0 Authentication and Management APIs.

Swap out:

global $auth0;

if (isset($_GET['code'])) {

    $userInfo = $auth0->getUser();
    // ...

with

global $auth0;

if (null !== $auth0->getExchangeParameters()) {
    $auth0->exchange();
    $userInfo = $auth0->getUser();
    // ...

And see if that works for you.

Hi @evansims,

Thanks for the response.

Currently I’m not getting code and login() function is not redirecting to callback url. Here is the code snippet

<?php

session_start(); 

require(__DIR__ . '/init.php');
require_once("modules/addons/auth0/auth0.php");

global $auth0;

if (!isset($_GET['code'])) {
    try {
        $auth0->login();
    } catch (\Exception $e) {
        error_log('Login error: '.$e->getMessage());
        exit;
    }
} else {
    try {
        $auth0->exchange();
        $credentials = $auth0->getCredentials();
        // Rest of your WHMCS code here...
    } catch (\Exception $e) {
        error_log('Exchange error: '.$e->getMessage());
    }
}

Hi @yrmallu :wave:

login() returns the URL your application should redirect to but does not issue the redirect itself. This permits your application to perform any last-minute hygiene before you actually redirect the user.

Your original code had the correct sentiment on sending the Location header itself.

Hi @evansims,

Thanks for the quick reply.

Sorry I didn’t get. What was the wrong with code?

It’s not redirecting to Auth0 portal for asking login details and appending code to the url.

Thanks

No worries, this is what I’m referring to:

    try {
        $auth0->login();
    } catch (\Exception $e) {

That login() call should instead read:

    try {
        Header('Location: ' . $auth0->login());
        exit;
    } catch (\Exception $e) {

So that the user is redirected for the authentication flow. They’ll be returned to your app and that point the callback parameters will be present

Hi @evansims,

Now redirecting is working fine and able to login into Auth0 but after login
I’m getting

[Tue Jul 23 16:09:20.369259 2024] [php:notice] [pid 1802352] [client 192.168.81.227:63909] Exchange error: Invalid state
type or paste code here

Here the code

<?php

session_start(); // Make sure this is the first thing in your script.

require(__DIR__ . '/init.php');
require_once("modules/addons/auth0/auth0.php");

global $auth0;

if (!isset($_GET['code'])) {
    try {
    try {
        // get intended login Url
        $loginUrl = $auth0->login();

        // redirect the user to the login Url
        header('Location: ' . $loginUrl);
        exit;
    } catch (\Exception $e) {
        error_log('Login error: '.$e->getMessage());
    }

} else {
    try {
        $auth0->exchange();
        $credentials = $auth0->getCredentials();
        // Rest of your WHMCS code here...
    } catch (\Exception $e) {
        error_log('Exchange error: '.$e->getMessage());
    }
}

Before diving in further, can you confirm you don’t have any old cookies hanging around from previous attempts? That could be contributing to an invalid state error.

Hi @evansims,

I have cleared all cache and cookies on Brower but still getting the same error

Hi @evansims,

Just to let you know, I’m sharing the debugging state information

[Tue Jul 23 17:08:31.370121 2024] [php:notice] [pid 1802895] [client 192.168.81.227:50078] Generated State: 2fc15f86f6fc08bf976a473b314fbcdd
[Tue Jul 23 17:08:45.449470 2024] [php:notice] [pid 1802892] [client 192.168.81.227:50086] State returned from Auth0: 2fc15f86f6fc08bf976a473b314fbcdd

But still getting the same error

Hmm, bizarre. An Invalid State can only be thrown if those two values don’t match, so something is off somewhere.

  • Could you share how you’re initializing the Auth0 class? Minus any sensitive data, of course. :smile:
  • Could you elaborate on the specific SDK and PHP versions you are using in your environment and confirm that you have the OpenSSL extension enabled for PHP?

@evansims Sure. Here the required information.

initializing the Auth0 class

$configuration = new SdkConfiguration(
    domain: $domain,
    clientId: $client_id,
    clientSecret: $client_secret,
    cookieSecret: $secret,
    cookieExpires: 3600,
    redirectUri: $redirect_uri
);

$auth0 = new Auth0($configuration);

PHP Version

PHP 8.1.2-1ubuntu2.18 (cli) (built: Jun 14 2024 15:52:55) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.2, Copyright (c) Zend Technologies
    with the ionCube PHP Loader v13.0.2, Copyright (c) 2002-2023, by ionCube Ltd.
    with Zend OPcache v8.1.2-1ubuntu2.18, Copyright (c), by Zend Technologies

And also OpenSSL extension enabled for PHP.

Hi @yrmallu

Thanks for those additional details. Things look OK there.

Could you please run the following from your project’s root directory and share the resulting output here?

composer show

I’m wondering if that session_start() call is potentially messing with the cookie writing. I’d recommend removing that unless it’s absolutely necessary. Otherwise, you might need to use the SessionStore for session and transient storage with the SDK, as opposed to the default CookieStore.

<?php
use Auth0\SDK\Auth0;
use Auth0\SDK\Configuration\SdkConfiguration;
use Auth0\SDK\Store\SessionStore;

$configuration = new SdkConfiguration(
    domain: $domain,
    clientId: $client_id,
    clientSecret: $client_secret,
    cookieSecret: $secret,
);

$sessionStorage = new SessionStore($configuration, $configuration->getSessionStorageId());
$transientStorage = new SessionStore($configuration, $this->getTransientStorageId());

$configuration->setSessionStorage($sessionStorage);
$configuration->setTransientStorage($transientStorage);

$auth0 = new Auth0($configuration);

(SessionStore internally sets up the session using session_start() on it’s own, so you won’t need to make that call yourself if you use it, unless you need session access prior to initializing the SDK.)

@evansims Here is the

composer show

results

auth0/auth0-php                                8.11.1 PHP SDK for Auth0 Authentication and Management APIs.
composer/semver                                3.4.2  Semver library that offers utilities, version constraint pars...
nyholm/psr7                                    1.8.1  A fast PHP7 implementation of PSR-7
php-http/discovery                             1.19.4 Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implemen...
php-http/multipart-stream-builder              1.3.1  A builder class that help you create a multipart stream
psr-discovery/all                              1.0.1  Lightweight library that discovers available PSR implementati...
psr-discovery/cache-implementations            1.1.1  Lightweight library that discovers available PSR-6 Cache impl...
psr-discovery/container-implementations        1.1.1  Lightweight library that discovers available PSR-11 Container...
psr-discovery/discovery                        1.1.1  Lightweight library that discovers available PSR implementati...
psr-discovery/event-dispatcher-implementations 1.1.1  Lightweight library that discovers available PSR-14 Event Dis...
psr-discovery/http-client-implementations      1.2.0  Lightweight library that discovers available PSR-18 HTTP Clie...
psr-discovery/http-factory-implementations     1.1.1  Lightweight library that discovers available PSR-17 HTTP Fact...
psr-discovery/log-implementations              1.0.1  Lightweight library that discovers available PSR-3 Log implem...
psr/cache                                      3.0.0  Common interface for caching libraries
psr/container                                  2.0.2  Common Container Interface (PHP FIG PSR-11)
psr/event-dispatcher                           1.0.0  Standard interfaces for event handling.
psr/http-client                                1.0.3  Common interface for HTTP clients
psr/http-factory                               1.1.0  PSR-17: Common interfaces for PSR-7 HTTP message factories
psr/http-message                               2.0    Common interface for HTTP messages
psr/log                                        3.0.0  Common interface for logging libraries

@evansims Thanks for your suggestion.

I have removed session_start() and added suggested SessionStore code.
Now I’m getting new error

[Tue Jul 23 17:52:23.946676 2024] [php:error] [pid 1802892] [client 192.168.81.227:55173] PHP Fatal error:  Declaration of GuzzleHttp\\Psr7\\Request::getRequestTarget() must be compatible with Psr\\Http\\Message\\RequestInterface::getRequestTarget(): string in /var/www/html/whmcs/vendor/guzzlehttp/psr7/src/Request.php on line 58
[Tue Jul 23 17:52:23.963816 2024] [php:notice] [pid 1802892] [client 192.168.81.227:55173] [WHMCS Application] ERROR: Whoops\\Exception\\ErrorException: Declaration of GuzzleHttp\\Psr7\\Request::getRequestTarget() must be compatible with Psr\\Http\\Message\\RequestInterface::getRequestTarget(): string in /var/www/html/whmcs/vendor/guzzlehttp/psr7/src/Request.php:58 Stack trace: #0 /var/www/html/whmcs/vendor/whmcs/whmcs-foundation/lib/Utility/Error/Run.php(0): WHMCS\\Utility\\Error\\Run->handleError() #1 [internal function]: WHMCS\\Utility\\Error\\Run->handleShutdown() #2 {main} {"exception":"[object] (Whoops\\\\Exception\\\\ErrorException(code: 64): Declaration of GuzzleHttp\\\\Psr7\\\\Request::getRequestTarget() must be compatible with Psr\\\\Http\\\\Message\\\\RequestInterface::getRequestTarget(): string at /var/www/html/whmcs/vendor/guzzlehttp/psr7/src/Request.php:58)"} []

Hi @yrmallu

Thanks for the additional detail. Interesting, I’m not sure what would be importing Guzzle from your composer show output there. We don’t import that on the SDK side.

In any case, it sounds like there might be an old version of the Guzzle dependency floating around somewhere in your project that isn’t compatible with the latest HTTP PSRs.

Can you try deleting your composer.lock file and vendor subdirectory and run composer install to fetch a fresh dependency tree?

Hi @evansims,
Guzzle came along with composer install. Is there any way to exclude?

Thanks

I removed composer.lock and vendor and installed composer freshly but still getting the same error.

The error is coming on $auth0->exchange()