Using react-router and reacts context api i am attempting to authenticate a user in Auth0 and then redirect the user to a home page on successful login. What am i missing in my routing? I am passing the context to the rest of the app by wrapping the app using and passing the login and logout methods down the tree. When I call the login() method from a child component the login method in the context gets called where i set state {isAuthenticated} to true and then i call the Auth.login() from within the context component as shown in the attached code.
I have searched Auth0 docs and have setup the correct callback route to http://localhost:3000/admin/dashboard
When i click login on the page the first time the appropriate Auth0 component shows up and i can login, however after i enter credentials and hit the login button i am redirected back to the {Login} component. If i then hit the login button in the {Login} component again after that i can see the page trying to redirect to my home route and then it kicks me back to the {Login} component.
import auth from '../Auth/Auth'
const AuthContext = React.createContext();
const Auth = new auth();
class AuthProvider extends React.Component {
state = { isAuthenticated: false }
constructor() {
super()
this.login = this.login.bind(this)
this.logout = this.logout.bind(this)
}
auth0Signin() {
Auth.login()
}
login() {
this.setState({ isAuthenticated: true })
this.auth0Signin()
}
logout() {
Auth.logout()
this.setState({ isAuthenticated: false })
}
render() {
return (
<AuthContext.Provider
value={{
isAuthenticated: this.state.isAuthenticated,
login: this.login,
logout: this.logout
}}
>
{this.props.children}
</AuthContext.Provider>
)
}
}
const AuthConsumer = AuthContext.Consumer
export { AuthProvider, AuthConsumer };
import Auth from './Auth/Auth'
import Callback from './views/components/Callback'
import { createBrowserHistory } from "history";
import React from 'react';
import "react-notification-alert/dist/animate.css";
import { Redirect, Route, Router, Switch } from "react-router-dom";
import { AuthConsumer, AuthProvider } from './providers/AuthContext';
import Login from './views/pages/Login';
import Register from './views/pages/Register'
import Dashboard from './layouts/Admin/Admin'
const auth = new Auth()
const hist = createBrowserHistory();
const handleAuthentication = (nextState, replace) => {
if (/access_token|id_token|error/.test(nextState.location.hash)) {
auth.handleAuthentication();
}
}
const ProtectedRoute = ({ component: Component, ...rest }) => (
<AuthConsumer>
{({ isAuthenticated, login, logout}) => (
<Route
render={props =>
isAuthenticated ? <Component login={login} logout={logout} isAuthenticated={isAuthenticated} {...props} /> : <Redirect to='/auth/login' />}
{...rest}
/>
)}
</AuthConsumer>
)
const PublicRoute = ({ component: Component, ...rest }) => (
<AuthConsumer>
{({ isAuthenticated, login, logout}) => (
<Route
render={props =><Component login={login} logout={logout} isAuthenticated={isAuthenticated} {...props} /> }
{...rest}
/>
)}
</AuthConsumer>
)
const App = () => {
return (
<>
<Router history={hist}>
<AuthProvider>
{/* <Header /> */}
<Switch>
<ProtectedRoute path='/admin/dashboard' component={Dashboard} />
<PublicRoute path='/auth/Register' component={Register} />
<PublicRoute path='/auth/Login' component={Login} />
<Route path="/callback" render={(props) => {
handleAuthentication(props);
return <Callback {...props} />
}}/>
<PublicRoute path='/' component={Login} />
</Switch>
{/* <Footer /> */}
</AuthProvider>
</Router>
</>
);
}
export default App;
import auth0 from 'auth0-js';
import { AUTH_CONFIG } from './auth0-variables';
import history from './History';
export default class Auth {
auth0 = new auth0.WebAuth({
domain: AUTH_CONFIG.domain,
callbackUrl: AUTH_CONFIG.callbackUrl,
clientID: AUTH_CONFIG.clientId,
redirectUri: AUTH_CONFIG.redirectUri,
responseType: 'token id_token',
scope: 'openid'
});
login() {
this.auth0.authorize();
}
constructor() {
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
this.handleAuthentication = this.handleAuthentication.bind(this);
this.isAuthenticated = this.isAuthenticated.bind(this);
this.getAccessToken = this.getAccessToken.bind(this);
this.getIdToken = this.getIdToken.bind(this);
this.renewSession = this.renewSession.bind(this);
}
handleAuthentication() {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
} else if (err) {
history.replace('/admin/dashboard');
console.log(err);
alert(`Error: ${err.error}. Check the console for further details.`);
}
});
}
getAccessToken() {
return this.accessToken;
}
getIdToken() {
return this.idToken;
}
setSession(authResult) {
// Set isLoggedIn flag in localStorage
localStorage.setItem('isLoggedIn', 'true');
// Set the time that the Access Token will expire at
let expiresAt = (authResult.expiresIn * 1000) + new Date().getTime();
this.accessToken = authResult.accessToken;
this.idToken = authResult.idToken;
this.expiresAt = expiresAt;
// navigate to the home route
history.replace('/home');
}
renewSession() {
this.auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
} else if (err) {
this.logout();
console.log(err);
alert(`Could not get a new token (${err.error}: ${err.error_description}).`);
}
});
}
logout() {
// Remove tokens and expiry time
this.accessToken = null;
this.idToken = null;
this.expiresAt = 0;
// Remove isLoggedIn flag from localStorage
localStorage.removeItem('isLoggedIn');
this.auth0.logout({
returnTo: window.location.origin
});
// navigate to the home route
history.replace('/auth/login');
}
isAuthenticated() {
// Check whether the current time is past the
// access token's expiry time
let expiresAt = this.expiresAt;
return new Date().getTime() < expiresAt;
}
}
The way I do it is with history.replace() in the Auth utility methods and then redirect to an authcheck component which upodates the global context state.
import auth0 from 'auth0-js'
import history from './history';
export default class Auth {
auth0 = new auth0.WebAuth({
domain: 'webapp1.auth0.com',
clientID: 'uZxUdMAsiDWeu3OrNpoi4JwJscdF5nAx',
redirectUri: 'http://localhost:3000/callback',
responseType: 'token id_token',
scope: 'openid profile email'
})
userProfile = {}
login = () => {
this.auth0.authorize()
}
handleAuth = () => {
this.auth0.parseHash((err, authResult) => {
if(authResult) {
localStorage.setItem('access_token', authResult.accessToken)
localStorage.setItem('id_token', authResult.idToken)
let expiresAt = JSON.stringify((authResult.expiresIn * 1000 + new Date().getTime()))
localStorage.setItem('expiresAt', expiresAt)
this.getProfile();
setTimeout(() => { history.replace('/authcheck') }, 600);
} else {
console.log(err)
}
})
}
getAccessToken = () => {
if(localStorage.getItem('access_token')) {
const accessToken = localStorage.getItem('access_token')
return accessToken
} else {
return null
}
}
getProfile = () => {
let accessToken = this.getAccessToken()
if(accessToken) {
this.auth0.client.userInfo(accessToken, (err, profile) => {
if(profile) {
this.userProfile = { profile }
}
} )
}
}
logout = () => {
localStorage.removeItem('access_token')
localStorage.removeItem('id_token')
localStorage.removeItem('expiresAt')
setTimeout(() => { history.replace('/authcheck') }, 200);
}
isAuthenticated = () => {
let expiresAt = JSON.parse(localStorage.getItem('expiresAt'))
return new Date().getTime() < expiresAt
}
}
import React, { useEffect, useContext } from 'react';
import history from './history';
import Context from './context';
import * as ACTIONS from '../store/actions/actions';
import axios from 'axios';
const AuthCheck = () => {
const context = useContext(Context)
const send_profile_to_db = (profile) => {
const data = profile
axios.post('/api/posts/userprofiletodb', data )
.then(axios.get('/api/get/userprofilefromdb', {params: {email: profile.profile.email}})
.then(res => context.handleAddDBProfile(res.data)) )
}
useEffect(() => {
if(context.authObj.isAuthenticated()) {
context.handleUserLogin()
context.handleUserAddProfile(context.authObj.userProfile)
send_profile_to_db(context.authObj.userProfile)
history.replace('/')
}
else {
context.handleUserLogout()
context.handleUserRemoveProfile()
history.replace('/')
}
}, [])
return(
<div>
</div>
)}
export default AuthCheck;
You can check out the fully functioning app here