Horizon and Auth0
jirikolarik opened this issue ยท 11 comments
Hi, I'm trying to get embedded horizon (express.js) to work with Auth0.
When I use code from readme which redirects me outside, everything works just fine
horizon.authEndpoint('auth0').subscribe(() => {
window.location.replace(endpoint);
});
But when I try to use popup provided by Auth0 (Auth0-lock 10.2.0) and set token I got in callback like this:
const lock = new Auth0Lock(__AUTH_TOKEN__, __AUTH_DOMAIN__, {
auth: {
redirect: false,
}
});
lock.on('authenticated', (authResult) => {
horizon.utensils.tokenStorage.set(authResult.idToken);
});
lock.show();
then I get this error:
Horizon: clearing token storage since auth
Uncaught Error: JsonWebTokenError: invalid algorithm
Am I missing something?
Server version: 2.0.0
Client version: 2.0.0
Found it, horizon uses HS512
algorithm but AuthLock is using HS256
and then secret code used to verify the token is not taken from auth provider, but from horizon server options.
To keep horizon compatible with default auth0, we should allow to change algorithm on horizon or auth0-lock side
I am curious is it not possible to specify the signing algorithm on horizon side?
yeah, why don't we just pull out these two values as a option?
cli/src/make-token.js#L58
server/src/auth.js#L17
Just changing the signing algorithm wouldn't solve the problem right? Because the secrets are still different.
Can't we just authenticate ourselves with Horizon by supplying an Access Token obtained from Lock instead of the Authorisation Code?
Exactly, changing algorithm is not enough, secrets are different. If we do both, it still doesn't give us enough informations. Hashed response from horizon.authEndpoint
contains also horizon's users id (and it also creates new user, when he doesn't exists). This is missing when authenticating directly with auth0-lock. This is important also for React Native, there is no possibility to authenticate without it
@jirikolarik We use a WebView on React Native for now, but for a good user experience this has to be solved. It would be better if we add an extra endpoint to horizon server where you can specify an auth0 token instead of an auth0 code for authentication.
How does that work with WebView? I know that horizon on web takes parameters automatically from url on redirect back, but I couldn't image how would that work with React-Native, I have to try this approach temporarily.
Yes, for sure, I'd have to take a deeper look on horizon, how to achieve that, but unfortunately I have to move on with application and not spend hours/days with authentication. I'll let you know, when I make some progress on this
Bit off topic, but in React Native a web view accepts the prop
onNavigationStateChange={this.handleNavigationChange}
<WebView
source={{uri: this.state.authURL}}
style={styles.webview}
onNavigationStateChange={this.navigationChange}
startInLoadingState={true}
injectedJavaScript="document.getElementsByClassName('a0-footer'[0].parentNode.removeChild(document.getElementsByClassName('a0-footer')[0]);" // Remove the auth0 badge
/>
Then in handleNavigationChange
you can extract the token from the URL
import URL from 'url-parse';
navigationChange = (navState) => {
if (!navState.loading) {
const url = new URL(navState.url, true);
const token = url.query.horizon_token;
if (token) {
this.processToken(token);
}
}
};
After you intercepted the token, save it in the AsyncStorage
import RNRestart from 'react-native-restart';
processToken = async (token) => {
// Save token in async storage
try {
await AsyncStorage.setItem('horizon_token', token);
}
catch (error) {
console.warn(error); // eslint-disable-line no-console
}
// Immediately reload the React Native Bundle
RNRestart.Restart();
};
We reset the app completely after obtaining the token, but there should be another way to prevent reloading the entire app.
Then on start of the app:
function getToken() {
try {
return AsyncStorage.getItem('horizon_token');
}
catch (error) {
console.warn(error); // eslint-disable-line no-console
}
}
// Get token if it exists
let tokenOptions = {};
const token = await getToken();
// Token exists, supply token to horizon when setting up connection
if (token !== null) {
tokenOptions = {
authType: {
token,
storeLocally: false
}
}
}
// Create a horizon instance (with or without token as determined above)
const hz = new Horizon({
host: 'HOST',
secure: true,
keepalive: 50,
...tokenOptions
});
Works perfectly, thanks! I'm not sure, what's your reason for reloading entire application, but you can change horizons token on the fly by using horizon.utensils.tokenStorage.set(token)
and you're authenticated, try horizon.hasAuthToken()
and you will see, that everything worked as it should
Cool thanks! Didn't know about that functionality :)
@arthurvi Thanks for the solution, one advice I had is AsyncStorage isn't a great solution as its async characteristic. Most of the time it will return a promise instead of an actual token key.
My solution was using Realm instead (might need to created a customize Realm with schemas as a node modules and import from auth.js and from application, etc.)