Closed jeiman closed 6 years ago
Can you reproduce this in any way? Your code looks fine, but we need to find a way to reproduce the issue.
I'm having the same issue, happens of everly login, but on refresh, i can get profiles without issue.
Here's my auth component:
import Auth0Lock from 'auth0-lock';
import { withRouter } from 'react-router-dom';
import { Component } from 'react';
import PropTypes from 'prop-types';
class AuthProvider extends Component {
static propTypes = {
history: PropTypes.object.isRequired, // eslint-disable-line
children: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
profile: null,
};
this.history = props.history;
this.lock = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, {
autoclose: true,
auth: {
redirectUrl: AUTH_CONFIG.callbackURL,
responseType: 'token id_token',
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
params: {
scope: 'openid profile',
},
},
});
this.handleAuthentication();
// binds functions to keep this context
this.getProfile = this.getProfile.bind(this);
}
componentWillMount() {
const { userProfile, getProfile } = this;
if (!userProfile) {
getProfile((err, profile) => {
this.setState(state => ({
...state,
profile,
}));
});
} else {
this.setState(state => ({
...state,
profile: userProfile,
}));
}
}
getProfile(cb) {
const accessToken = this.getAccessToken();
if (accessToken) {
this.lock.getUserInfo(accessToken, (err, profile) => {
if (profile) {
this.userProfile = profile;
}
cb(err, profile);
});
}
}
getAccessToken = () => {
const accessToken = localStorage.getItem('accessToken');
if (!accessToken) {
console.error('No access token found');
}
return accessToken;
}
setSession(authResult) {
if (authResult && authResult.accessToken && authResult.idToken) {
// Set the time that the access token will expire at
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
localStorage.setItem('accessToken', authResult.accessToken);
localStorage.setItem('idToken', authResult.idToken);
localStorage.setItem('expiresAt', expiresAt);
// navigate to the home route
this.history.replace('/');
}
}
handleAuthentication() {
// Add a callback for Lock's `authenticated` event
this.lock.on('authenticated', this.setSession.bind(this));
// Add a callback for Lock's `authorization_error` event
this.lock.on('authorization_error', (err) => {
console.log(err); // eslint-disable-line no-console
this.props.history.replace('/');
});
}
isAuthenticated = () => {
// Check whether the current time is past the
// access token's expiry time
const expiresAt = JSON.parse(localStorage.getItem('expiresAt'));
return new Date().getTime() < expiresAt;
}
login = () => {
// Call the show method to display the widget.
this.lock.show();
}
logout = () => {
// Clear access token and ID token from local storage
localStorage.removeItem('accessToken');
localStorage.removeItem('idToken');
localStorage.removeItem('expiresAt');
// navigate to the home route
this.props.history.replace('/');
this.setState(() => ({
profile: null,
}));
}
render() {
const { children } = this.props;
return children({
profile: this.state.profile,
authInstance: this.lock,
login: this.login,
logout: this.logout,
authenticated: this.isAuthenticated(),
});
}
}
export default withRouter(AuthProvider);
and login:
import React from 'react';
import Button from 'components/button';
import Auth from 'components/auth';
import { withRouter } from 'react-router-dom';
const LoginPage = () => (
<div>
<Auth>
{({
login,
}) => (
<button
onClick={login}
className="
Bd(n)
Bg(n)
"
>
<Button>
Log In
</Button>
</button>
)}
</Auth>
</div>
);
export default withRouter(LoginPage);
thanks @VinSpee can you build a simple repro so we can isolate the issue?
I've seen this happening sometimes if the authorize request is made from a different origin than the callback URL. E.g. you navigate to "http://myapp.com", and instead of redirecting first to "https://myapp.com", it goes directly to "https://yourtenant.auth0.com/authorize?...&redirect_uri=https://myapp.com" (notice the difference between "http://" and "https://").
Since Auth0.js stores the state
in local storage before redirecting to Auth0 for later verification, you need to ensure that the origin where the authorize request originates is the same as the callback URL.
@luisrudge for easier troubleshooting, I might be a good idea to log in the console a little more data, like the state that came in the callback, and what was there in localStorage (no match?). Even a list of states present in LocalStorage might help.
Or maybe just handle one possible state storage slot instead (i.e. not including the state
value as part of the key for local storage)?
@nicosabena that's a nice idea, but always logging that would not be good. Maybe we should have a debug mode (manually set localStorage or ?debug in the url or something).
Here's a minimal repro:
code: https://github.com/VinSpee/auth0-debug live: https://recondite-lake.surge.sh/login
@VinSpee are you having this issue with google apps only?
I tested your app a few times and I always get the profile back. Do I have to do something different to see the error?
@luisrudge to be more clear: I am able to get the profile, but always get the console error:
Object { error: "invalid_token", errorDescription: "
state
does not match." }
@VinSpee ah, right. Sorry I missed that! Does this happen only with google?
i've only tried in w/ google auth, I'll try with another provider
I just tried it w/ GitHub as the provider and got the same error:
Object { error: "invalid_token", errorDescription: "state does not match." }
@VinSpee your code is running twice:
Every time you use the <Auth>
component, you're creating a new instance of Lock, and this is messing up the state handler.
So, it runs twice because you use the component both in the index route and in child routes: https://github.com/VinSpee/auth0-debug/blob/master/src/index.js#L20 https://github.com/VinSpee/auth0-debug/blob/master/src/Login.js#L6
We have a react sample here if you want to take a look.
You're also shipping two versions of react, because auth0-lock is using react@15, so you need to add this to your package.json file:
"resolutions": {
"react": "^16.2.0",
"react-dom": "^16.2.0"
}
I've been having the same problem, and I've found a workaround which I'll describe below just because I saw a lot of google results for this error, which seemed to be caused by all kinds of different things. However, I did find the proper solution, which is to use WebAuth.logout
rather than buildAuthorizeUrl
:
export function logout() {
webAuth.logout({
clientID: process.env.REACT_APP_AUTH0_CLIENT_ID,
responseType: 'token',
redirectUri: window.location.origin,
})
}
My problem was that I generated a logout url using buildAuthorizeUrl
with auth0-js v9.4.2. When I do this though, I redirect to the url manually, so the state
doesn't get set by auth0:
export function logout() {
const state = randomId()
// Save state to localstorage for Auth0
// https://auth0.com/docs/protocols/oauth2/oauth-state
localStorage.setItem('auth0-authorize', state)
const domain = process.env.REACT_APP_AUTH0_DOMAIN
const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID
const returnTo = webAuth.client.buildAuthorizeUrl({
clientID: clientId,
responseType: 'token',
redirectUri: window.location.origin,
state,
})
const params = stringify({returnTo, client_id: clientId})
window.location.assign(`https://${domain}/v2/logout?${params}`)
}
When someone hits the site and isn't authenticated, I redirect them to auth0 with authorize
. This sets the state in a different place in localstorage (seems to be a randomly generated string). To handle both my custom logout state and the built-in logout state, I'm providing an options parameter to parseHash
with the state set only if my custom key is set (being sure to clear my custom one when I use it to avoid always rejecting one set by auth0). Here's my code:
function checkAuthResult(err, authResult) {
if (err) {
// If we tried to log them in automatically, redirect to auth0
webAuth.authorize()
} else if (!authResult) {
// Try logging them automatically first
// https://auth0.com/docs/libraries/auth0js/v8#using-checksession-to-acquire-new-tokens
webAuth.checkSession({}, checkAuthResult)
} else if (authResult.accessToken) {
resolve(authResult)
}
}
// If we log out, auth0 doesn't set the state properly, so we have to do it manually.
// We can't do it every time though, because when they're trying to log from a url
// not generated by buildAuthorizeUrl, we need to let the default behavior work.
const state = localStorage.getItem('auth0-authorize')
const params = state ? {state} : {}
// Make sure to clear our custom state key
localStorage.setItem('auth0-authorize', '')
webAuth.parseHash(params, checkAuthResult)
@staab not sure I understand: why are you setting a state when doing a log out?
@luisrudge because I'm redirecting to the login page for auth0, which doubles as our landing page for folks who aren't signed in (I'm not using auth0-lock, and our software is login-only). As I understand it, there's no way to go to that page and have it successfully log someone in if state
isn't set. Also, as far as I can tell, this is exactly what the logout()
method was created for.
@staab
to log in with the universal login page, you do:
var options = {}; //anything you want, state is not required but you can use your own state here if you want
webAuth.authorize(options);
to log out from auth0, you do:
var options = {}; //again, no state required
webAuth.logout(options);
I mean, you're free to call the /v2/logout endpoint by yourself (although I don't see the point in doing it manually), but there's no need to send a state to the logout endpoint.
@luisrudge that's what I'm doing now (see the top of my original comment). I was mostly including my manual logout solution for posterity, since I hadn't seen the mistake I had made anywhere else after some googling.
After some more digging, it turns out that it's not logout that's passing state
, I'm redirecting back to my app after logging out, then calling authorize, which passes state. I had missed that step. So you're right, logout
doesn't use state
. To fix this, I'll see if I can pass an authorization url to redirectUri.
Ok, so I tried passing a redirectUri
built using buildAuthorizeUrl
with a custom managed state to get the app > logout > hosted login page redirect flow I want, but logout
always returns a 302 with Location: myappdomain
rather than the redirect_uri that gets passed via query parameters. I think this is what threw me off originally; is there a way to get logout
to set the Location
header to mydomain.auth0.com/login?...
with a valid state/audience/etc?
logout
expects a returnTo
param, not redirectUri
.
logout({
returnTo: 'https://myapp.com/login'
})
Ah ok thanks, that did the trick.
Now that my credibility has been destroyed by not being able to read the documentation properly, is redirecting to the login page straight from the logout endpoint a common use case or am I an outlier? If it's common, it would be really nice for logout/buildAuthorizeUrl to handle setting state
the same way authorize
does.
from logout directly to the universal hosted login page is the first time I saw it 😝 The best way to do what you want is:
your app > click logout > webauth.logout({returnTo: 'https://myapp.com/login'})
> https://myapp.com/login > webAuth.authorize()
> universal login page.
So, you create a step in between the logout and redirecting to the universal login page and, in this new step, you call authorize(). Then auth0.js will handle all the state and everything should work!
Fair enough! I had it set up the way you described before, but I figured saving a redirect to my tool would be the best use of our resources and user experience. Not a big deal in any event though. Thanks for the help!
I have come upon this error due to failure to JSON.stringify my state parameters before sending them to auth0 for authorize. Rather annoying error for a simple mistake. Auth0 library should really verify that the state is a "string" as objects don't work so well.
@bramski what was the error? we want to support objects so you can restore an app state if you need to
@luisrudge Here's the problem:
same error as @VinSpee .But it works fine with google chrome .In mozilla,Error showing only in the first time of login and after the cookie added it works.
What was the final resolution for the error? I also get the 'state does not match' error the first time and it goes straight through the second.
@nicosabena mention of HTTP vs HTTPS made the difference for me.
Fix: Now forcing all traffic to HTTPS and all requests work.
Previously, a user would enter on HTTP and try to authenticate. The callback was set to send them back to the domain but on HTTPS, hence the mixed/non-matching state. As the callback was set to return to HTTPS, the second and all subsequently requests worked as expected. It was no longer mixed or not matching. It was HTTPS from there on out.
same error as @VinSpee .But it works fine with google chrome .In mozilla,Error showing only in the first time of login and after the cookie added it works.
Same here - except for me it's with certain Internet Explorer users (not all). After several attempts it lets them in. There is definitely a bug here, but the documentation fails to mention what causes this error and any attempts to solve it.
Also, I think there might be a timing issue here. It's as if it can't read the cookie (auth0 uses a cookie ?) the first time on the request/response.
What was the final resolution for the error? I also get the 'state does not match' error the first time and it goes straight through the second.
Lastly, I am https to https - I get it both with http to https and https to https.
When I changed the AUTH_CONFIG to the one below, it worked for me. I think there was an issue with the way the callback URL was being created but I'm not sure.
export const AUTH_CONFIG = { domain: 'fcmcms.eu.auth0.com', clientId: 'f$%*$$%*$%$*%$*$$%$*y', callbackUrl: process.env.NODE_ENV === 'development' ? 'http://localhost:3000/callback' : 'https://domain.com/callback', }
@DarryllRobinson how it was before?
I want to summarize the two common causes I see in customer support related to this state does not match
error (apologies for repeating the content of my previous post, but I'm adding a second cause here):
http://myapp.com
and the callback is set to https://myapp.com
(note the different http
vs https
scheme). Auth0.js/Lock store the state
in local storage, which is not shared across different domains. In the http
/https
case, the correct approach is to redirect to the https
scheme first and then initiate the login.parseHash
to be called twice. The first time it's run, the slot in local storage used for the specified state is cleared, so the second time you will always get this error. Make sure that your app only calls this once. Note that, by default, Lock calls it automatically upon initialization if it finds a result in the location of the hosting window.Another super common case is when your users bookmark the /login endpoint instead of your app's url. This will cause the parseHash
method to fail as well.
@DarryllRobinson how it was before?
Sorry, I meant to say I had updated it to: callbackUrl: window.location.origin + '/callback'
I get this error in Private Browsing Firefox v63.0.3 with all extensions and Content Blocking disabled using the provided React sample app. To reproduce, login and then refresh the page, auth0-js v9.8.2. I do not get the error in Chrome or Safari.
I want to summarize the two common causes I see in customer support related to this
state does not match
error (apologies for repeating the content of my previous post, but I'm adding a second cause here):
- Callback URL set to a different domain. E.g. your app started at the
http://myapp.com
and the callback is set tohttps://myapp.com
(note the differenthttp
vshttps
scheme). Auth0.js/Lock store thestate
in local storage, which is not shared across different domains. In thehttp
/https
case, the correct approach is to redirect to thehttps
scheme first and then initiate the login.- A flow in the app is causing
parseHash
to be called twice. The first time it's run, the slot in local storage used for the specified state is cleared, so the second time you will always get this error. Make sure that your app only calls this once. Note that, by default, Lock calls it automatically upon initialization if it finds a result in the location of the hosting window.
For the latter, I noticed that my /callback
page was being called a second time by browser-sync
. The error went away when I added the following to my browser-sync
configuration (https://browsersync.io/docs/options#option-snippetOptions):
snippetOptions: {
ignorePaths: "callback"
}
I get this error in Private Browsing Firefox v63.0.3 with all extensions and Content Blocking disabled using the provided React sample app. To reproduce, login and then refresh the page, auth0-js v9.8.2. I do not get the error in Chrome or Safari.
We are hitting the same thing - refreshing the page shows invalid token - state does not match
for Firefox only.
Hello,
I have made this todo app with auth0 todo app. Everything works fine on chrome & edge. On Mozilla, I am able to login/signup, and after that even add new todos, but I can't update existing todos, the only thing that shows up in the console is state
does not match. This is also the case when https to https.
@luisrudge Can we get this re-opened? It's still an issue in Firefox.
Can you build and deploy a repro project? Also, make sure you are not doing any of the common mistakes that yield this error: https://github.com/auth0/auth0.js/issues/655#issuecomment-419966421
@luisrudge, the repro project is the official Auth0 React Sample Login project, https://github.com/auth0-samples/auth0-react-samples/tree/master/01-Login. Clone it, set the domain
and clientId
in auth0-variables.js
, run npm start
, login, and refresh the page in Firefox. You'll get "state
does not match.".
I am also seeing this problem in Firefox and am able to reproduce it using the official Auth0 Vue sample Login Project: https://github.com/auth0-samples/auth0-vue-samples/tree/master/01-Login
I notice when I refresh the page it goes back to the callback url. So maybe it has to do with how Firefox performs a page refresh?
Can you record a HAR file of this? I just came back from vacations and I'll take a look at this during this week.
Attached is a har file: Archive 19-01-07 09-14-54.zip
@luisrudge here's the HAR file for the todo app I created https://drive.google.com/drive/folders/1LHAy05zQTvuc1MQJjx7OWYSEYESRwJe-?usp=sharing
One of the main issues with this bug is Auth0 is missing something in their explanation. Everyone says 'make sure not sending twice', 'http to https' etc... but it's more than that.
I've tried all the above, sent HAR files, and still haven't had an explanation of the issue.
Works fine in all browsers but IE. In IE, I've had to do a retry work around to get past this issue.
On Mon, Jan 7, 2019 at 8:49 AM Luís Rudge notifications@github.com wrote:
Can you record a HAR file of this? I just came back from vacations and I'll take a look at this during this week.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/auth0/auth0.js/issues/655#issuecomment-451940264, or mute the thread https://github.com/notifications/unsubscribe-auth/AAIFNBCEKUhb7m0mILXo0OXB77CIAproks5vA1B0gaJpZM4R8Xka .
So for some reason I can't seem to authenticate users on certain computers/laptops that has the same browser versions.
auth.js
Login.vue
Picture:
Other details:
Auth0 version: NPM -
8.12.2
ScriptJS -8.12.2
Browser: Chrome Version 64.0.3282.140 (Official Build) (64-bit) O.S: Windows 10
The issue is not happening on my laptop (which is running on the the Browser version mentioned above), but it is happening on other laptops (same browser version).
Not too sure what else to try here. Been looking at the forums, nothing is working out.
Would love some insights on this. Thank you.