I'm working on adding Auth0 authentication to my React app, and even though I have it working, I feel like there's a better way to approach this. I'm struggling to figure out a better pattern for the authentication logic.
The app setup is react + redux + react-router-redux + redux-saga + immutable + auth0-lock.
Beginning at the top, the App
component defines the basic page layout, both Builder
and Editor
components require the user to be logged in, and authenticated()
wraps each in a Higher Order Component responsible for handling authentication.
// index.js
import App from './containers/App';
import Builder from './containers/Builder';
import Editor from './containers/Editor';
import Home from './containers/Home';
import Login from './containers/Login';
import AuthContainer from './containers/Auth0/AuthContainer';
...
ReactDOM.render(
<Provider store={reduxStore}>
<Router history={syncedHistory}>
<Route path={'/'} component={App}>
<IndexRoute component={Home} />
<Route path={'login'} component={Login} />
<Route component={AuthContainer}>
<Route path={'builder'} component={Builder} />
<Route path={'editor'} component={Editor} />
</Route>
</Route>
<Redirect from={'*'} to={'/'} />
</Router>
</Provider>,
document.getElementById('app')
);
At the moment, AuthContainer
doesn't do much except check the redux store for isLoggedIn
. If isLoggedIn
is false, the user is not allowed to view the component, and is redirected to /login
.
// containers/Auth0/AuthContainer.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { redirectToLogin } from './Auth0Actions';
class AuthContainer extends React.Component {
componentWillMount() {
if (!this.props.isLoggedIn) {
this.props.actions.redirectToLogin();
}
}
render() {
if (!this.props.isLoggedIn) {
return null;
}
return this.props.children;
}
}
// mapStateToProps(), mapDispatchToProps()
export default connect(mapStateToProps, mapDispatchToProps)(AuthContainer);
The next piece is Auth0. The Auth0 Lock works in "redirect" mode, which means the user will leave the app to log in, and then be redirected back to the app at /login
. As part of the redirect, Auth0 attaches a token as part of the URL, which needs to be parsed when the app loads.
const lock = new Auth0Lock(__AUTH0_CLIENT_ID__, __AUTH0_DOMAIN__, {
auth: {
redirect: true,
redirectUrl: `${window.location.origin}/login`,
responseType: 'token'
}
});
Since Auth0 will redirect to /login
, the Login
component also needs authentication logic. Similar to AuthContainer
, it checks the redux store for isLoggedIn
. If isLoggedIn
is true, it redirects to the root /
. If isLoggedIn
is false, it'll attempt to authenticate.
// containers/Login/index.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { authenticate, redirectToRoot } from '../Auth0/Auth0Actions';
class Login extends React.Component {
componentDidMount() {
if (!this.props.isLoggedIn) {
this.props.actions.authenticate();
}
else {
this.props.actions.redirectToRoot();
}
}
render() {
return (
<div>Login Page</div>
);
}
}
// mapStateToProps(), mapDispatchToProps()
export default connect(mapStateToProps, mapDispatchToProps)(Login);
With these pieces in place, my integration with Auth0 seems to be working. However, I now have AuthContainer
and Login
component, and they are very similar. I can't place the Login
component as a child to AuthContainer
since the login page does not actually require the user to be logged in.
Ideally, all authentication logic lives in one place, but I'm struggling to figure out another way to get it working, especially with the special case of the Auth0 redirect. I can't help but think that there must be a different approach, a better pattern for authentication flow in a react + redux app.
One thing that would be helpful is to better understand how to dispatch an async action on page load, before the app starts initializing. Since Auth0 works with callbacks, I'm forced to delay setting the redux initial state until after Auth0 invokes the registered callback. What is the recommended way to handle async actions on page load?
I've left out some pieces for brevity, like the actions and sagas, but I'll be more than happy to provide those if it'll be helpful.
I'm doing the same thing in my project and working fine with
redux
,react-router
, just have a look at my code below:routes:
AuthenticatedComponent:
notAuthenticatedComponent: