State paremeter woes

Hey friends!

I have a rather peculiar issue that I think I have localized to a certain use case and environment, but have had little luck with resolving sadly.

My issue stems from the “state” variable on our production deployment of the Auth0 client in one of our web applications. The web application is running a PHP back end and we have separate “applications”/client keys for both debugging and production.

When we deploy to production we have sifted the logs and found we are getting a “invalid state” error and after a few page clicks the user is kicked and needs to re-authenticate.

I was doing some research and found this article Troubleshoot WordPress Plugin Invalid State Errors. It was very helpful although the article seems to be written for WordPress when we’re running in-house scratch PHP code.

I followed the steps where it asked to verify the proper variables and see that the cookie variables are being set and there is an “auth-0-state” variable set.

It doesn’t appear to be that the variable is setting. However when the user authenticates the resulting GET request has a state. We have tried manually setting this variable to a session variable with the same index but the error seems to persist.

Could anyone elaborate on the proper way to set and validate a state variable within the PHP SDK? I’m happy to provide code samples within reason but naturally I may have to redact some information.

If it helps, we have a standard login, logout page, and a file with all the auth0 resources/variables in it. In the resource bundle we instantiate a auth0 object/client which we then use to handle all the authentication (as is directed in the documentation IIRC).

Thanks so much to anyone that can help!

When we deploy to production we have sifted the logs and found we are getting a “invalid state” error and after a few page clicks the user is kicked and needs to re-authenticate.

Do you see this problem for every interaction, or you are seeing this in your PHP logs as an intermittent issue?

You say that this happens after a few page clicks. Often, the state check should be done only once, right after the user authenticates (on the callback processing).

The flow is usually something like this:

  • Application triggers an authentication request.
    • A state is generated and stored (usually in a cookie for web applications).
    • The user is redirected to Auth0’s /authorize endpoint, including the generated state as part of the request.
  • Auth0 takes the user through all the authorization hooplas required.
  • Auth0 redirects back to the application’s callback URL with the response. The response includes the state originally provided.
  • The application checks that the state returned matches the one that was stored. If it doesn’t, the response is rejected (“Invalid state”). This prevents CSRF login attacks.

The auth0-php SDK

If you are using the auth0-php SDK, you shouldn’t need to worry about generating, storing or comparing state, as this is all handled by the SDK. Let me go through the logic (as used in the quickstart code) in case it helps with the troubleshooting:

  • When you start the authentication with $auth0->login() (see this sample) the SDK will use a state you provide or generate a new one (code here).
  • The SDK will, by default, store the state in a session cookie prefixed with auth0_, but you can change both the cookie name or the session store method altogether if you prefer, by providing a state_handler value in the constructor (see https://github.com/auth0/auth0-PHP/blob/989e8dcd48e071fdaa92dcd7dfd521c596f5458c/src/Auth0.php#L229-L259).
  • When receiving the response from Auth0 (this is triggered when you invoke $auth0->getUser() from the callback handler), the SDK will retrieve the stored state (see here) and compare it with the value received, ending with a “Invalid state” error if they are different.

User landing on the hosted login page by mistake

If you see this problem only intermittently, it might be caused by users landing in the Auth0’s login page by clicking the back button or bookmarking the login page instead of bookmarking your app.
In these cases (where the application didn’t request the authentication), Auth0 will try to finish the flow anyway and send a response to the application (this is done mainly for compatibility reasons with older apps). The problem is that this response will not contain a state that the application recognizes, so it will end up in a “Invalid state” error for modern apps.

You can configure Auth0 so that if the user lands on the login page by mistake (again, by clicking the back button or by bookmarking the login page) Auth0 redirects the user back to the application’s login initiation endpoint (e.g.https://myapp.com/login), so that the app can start a proper authentication flow (with a generated state and everything). See Configure Default Login Routes for details about this.

Checking the HTTPS flow

If you analyze the requests with the browser’s developer tools, you should see an /authorize request containing the state generated in the query string when the authentication flow starts:

https://{your_auth0_domain}/authorize?[....]&state=xxxxxx

and, at the end of the flow, either a redirect or a POST to the application callback URL, with a code (that the application will exchange for a token response) and the state in the query string or as the body of the POST. E.g:

https://{your_app_callback_url}?code=yyyyy&state=xxxxx

Note that between those requests you might see many transitions (especially if the user needs to authenticate, do MFA or consent) with internal states being used. You can ignore these. The ones that you care about are the first /authorize redirection from your app, and the final response to the application’s callback URL.

3 Likes

Hey Nicholas! Thanks for the quick response. And thanks for the quite verbose response.

To clarify we are seeing the log hit pretty much every refresh.

I can see a few opportunities right away. I think that since we have the auth0 client instantiaton and auth0->login() calls in different files that causes an issue.

Let me tinker around and see if I can implement a proper solution. I’ll happily report back with whatever solution I can find.

@drewHolmes - If you find the Auth0 class methods a little too heavy-handed (they’re built to be fairly single-purpose), you might want to look at the Authentication class to do what you need. The “Regular Web app Login Flow” section on the page below goes more in-depth and might be helpful in either troubleshooting or coming up with a better solution:

2 Likes

Hey Josh! Thanks for the reply - and apologies for the long response on my end. I’ll give these docs a look and see if I come up with a solution and report back!

1 Like

Ok. So after a few hours of debugging here is what I’ve discovered.

I have tried implementing the " Using the Authentication API with Auth0-PHP" solution. I first tried to mesh everything into our current auth0 login flow and some things are going a bit haywire.

I was able to get the example to work and redirect back to the “landing page” (the callback URL in the client) but I’m not sure how to access the user data since it is now stored in a sessionStore object. I have tried calling $session_store->get('user') to no avail. Also it appears when I check the cookies that the state parameter is still not showing up.

So I decided to try the example code as it is exactly in the documentation. I did have to change some variables around as I don’t have my getenv() configured correctly. Some guidance on getting that .env file located/setup would be much appreciated!

So I’m able to get to the auth-required.php page and it presents me with the loging view. Once I authenticate it appears the page hangs on *tenant name*/login/callback?code=blahblahblah

I feel like we are REALLLY close but the devils in the details. Any insight would be much appreciated!

Update:

I found under the “Local Storage” section of the Google Chrome Developer console that there are a number of Auth0 entries. A dump of one of these as a sample shows

1. {nonce: null, state: "-hGByn0wxYBCjPXgC3lCbiHQ2FNQR~rU", lastUsedConnection: "google-oauth2"}

  1. lastUsedConnection: "google-oauth2"
  2. nonce: null
  3. state: "-hGByn0wxYBCjPXgC3lCbiHQ2FNQR~rU"

There is a state value stored here but it is not in the cookie for the site. Should it be in one place or the other? Is there a way to make this state value persist so my users don’t get kicked?

Also worth noting, our application utilizes custom PHP session names as a deterrent to CSRF. I’m wondering if the custom session names are causing an issue with the Auth0 handshake.

That’s the “best” way to do it, though it should be right there in your $_SESSION global if you’re debugging. The standard way is \Auth0\SDK\Auth0::getUser().

The session cookie points to the session data. There won’t be a cookie specifically for state (that might have not been clear in Nico’s response above).

Environment variables, beyond what’s set at the OS level (bash profile), are not automatically populated in your application. There are a number of libraries out there that will parse your .env file and one of them is covered here:

I’m not sure what you’re saying here. Can you clarify what you mean here?

Nothing in the SDK stores data in your browser. The only artifact there for a regular web application like this is the session cookie, which is generated and managed in PHP itself.

That state value is ephemeral, once it’s validated (or fails validation) it’s deleted. Once you get an invalid state exception, it’s gone. Best place to check what’s saved and what’s returned is right before it’s validated:

If you’re just outputting to the screen for validation, dump $_SESSION and $_REQUEST and you should see where the mismatch is.

That should be configurable by implementing your own session store class but it’s possible you’re using it in a way we didn’t anticipate. If we can do something better there, please let me know!