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 state
s 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.