Auth0 default React example don't use react hooks.
I try to rewrite this example to use React Hooks.
Full Example
You can read full example in this repository
https://github.com/terrierscript/example-auth0/tree/full-example
Details
1. Create Context
First, I create AuthContext
that hold auth object and some auth result state.
// auth/AuthContext
import React, { createContext, useState, useContext } from 'react';
import { WebAuth } from 'auth0-js';
import { AUTH_CONFIG } from './auth0-variables';
const generateAuth = () =>
new WebAuth({
domain: AUTH_CONFIG.domain,
clientID: AUTH_CONFIG.clientID,
redirectUri: AUTH_CONFIG.callbackUrl,
responseType: 'token id_token',
scope: 'openid'
});
const Auth0Context = createContext<ReturnType<typeof useContextValue>>(null);
const useAuthState = () => {
return useState({
accessToken: null,
idToken: null,
expiresAt: 0
});
};
const useContextValue = () => {
const [authState, updateAuthState] = useAuthState();
return {
auth0: generateAuth(),
authState,
updateAuthState
};
};
export const Auth0Provider = ({ children }) => {
const value = useContextValue();
return (
<Auth0Context.Provider value={value}>{children}</Auth0Context.Provider>
);
};
export const useAuth0Context = () => {
return useContext(Auth0Context);
};
2. Create Context
Next, generate useAuth
.
Almost logics same as Auth.js
But isAuthenticated
changed from function
to boolean
value with useMemo
// src/useAuth
import { useCallback, useMemo } from 'react';
import history from '../history'; // TODO: history may pass from props
import { useAuth0Context } from './AuthContext';
const useIsAuthenticated = expiresAt => {
return useMemo(() => {
return new Date().getTime() < expiresAt;
}, [expiresAt]);
};
export const useAuth0 = () => {
const { auth0, authState, updateAuthState } = useAuth0Context();
const isAuthenticated = useIsAuthenticated(authState.expiresAt);
const login = useCallback(() => {
auth0.authorize();
}, [auth0]);
const logout = useCallback(() => {
updateAuthState({
accessToken: null,
idToken: null,
expiresAt: 0
});
localStorage.removeItem('isLoggedIn');
auth0.logout({
returnTo: window.location.origin
});
// navigate to the home route
history.replace('/home');
}, [auth0, updateAuthState]);
const setSession = useCallback(
authResult => {
localStorage.setItem('isLoggedIn', 'true');
let expiresAt = authResult.expiresIn * 1000 + new Date().getTime();
updateAuthState({
accessToken: authResult.accessToken,
idToken: authResult.idToken,
expiresAt: expiresAt
});
history.replace('/home');
},
[updateAuthState]
);
const renewSession = useCallback(() => {
auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
setSession(authResult);
} else if (err) {
logout();
console.error(err);
alert(
`Could not get a new token (${err.error}: ${err.error_description}).`
);
}
});
}, []);
const handleAuthentication = useCallback(() => {
auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
setSession(authResult);
} else if (err) {
history.replace('/home');
alert(`Error: ${err.error}. Check the console for further details.`);
}
});
}, []);
// retun some functions
return {
login,
logout,
handleAuthentication,
isAuthenticated,
renewSession
};
};
3. fix <Callback>
In base example, handleAuthentication
called in router
like this.
<Route path="/callback" render={(props) => {
handleAuthentication(props);
return <Callback {...props} />
}}/>
I feel it's so tricky.
But when use hooks, we call that with useEffect
// Callback/Callback
import React, { useEffect } from 'react';
import loading from './loading.svg';
import { useAuth0 } from '../Auth/useAuth';
export const Callback = props => {
const { handleAuthentication } = useAuth0();
const { location } = props;
useEffect(() => {
if (/access_token|id_token|error/.test(location.hash)) {
handleAuthentication();
}
}, [handleAuthentication, location]);
const style = {
//....
};
return (
<div style={style}>
<img src={loading} alt="loading" />
</div>
);
};
4. fix <App>
and <Home>
<App>
and <Home>
rewrited too.
<App>
call renewSession
with useEffect
// App
import React, { useCallback, useEffect, useMemo } from 'react';
import { Navbar, Button } from 'react-bootstrap';
import './App.css';
import { useAuth0 } from './Auth/useAuth';
const useGoToHandler = history => {
return useCallback(route => () => history.replace(`/${route}`), [history]);
};
export const App = ({ history }) => {
const { login, logout, isAuthenticated, renewSession } = useAuth0();
const goToHandler = useGoToHandler(history);
useEffect(() => {
if (localStorage.getItem('isLoggedIn') === 'true') {
renewSession();
}
}, [renewSession]);
return (
<div>
<Navbar fluid>
<Navbar.Header>
<Navbar.Brand>
<a href="#">Auth0 - React</a>
</Navbar.Brand>
<Button
bsStyle="primary"
className="btn-margin"
onClick={goToHandler('home')}
>
Home
</Button>
{!isAuthenticated && (
<Button
id="qsLoginBtn"
bsStyle="primary"
className="btn-margin"
onClick={login}
>
Log In
</Button>
)}
{isAuthenticated && (
<Button
id="qsLogoutBtn"
bsStyle="primary"
className="btn-margin"
onClick={logout}
>
Log Out
</Button>
)}
</Navbar.Header>
</Navbar>
</div>
);
};
// Home/Home
import React from 'react';
import { useAuth0 } from '../Auth/useAuth';
export const Home = () => {
const { login, isAuthenticated: isAuthenticated } = useAuth0();
return (
<div className="container">
{isAuthenticated && <h4>You are logged in!</h4>}
{!isAuthenticated && (
<h4>
You are not logged in! Please
<a style={{ cursor: 'pointer' }} onClick={login}>
Log In
</a>
to continue.
</h4>
)}
</div>
);
};
5. fix router
Rewrite router to this.
- Routers wrapped
<Auth0Provider>
. -
Callback
logic moved that component. - (trivial) Use
react-router
<Switch>
.
// roter
import React from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import { App } from './App';
import { Home } from './Home/Home';
import { Callback } from './Callback/Callback';
import history from './history';
import { Auth0Provider } from './Auth/AuthContext';
const Routes = () => {
return (
<Router history={history}>
<Route path="/" render={props => <App {...props} />} />
<Switch>
<Route path="/home" render={props => <Home {...props} />} />
<Route path="/callback" render={props => <Callback {...props} />} />
</Switch>
</Router>
);
};
export const makeMainRoutes = () => {
return (
<Auth0Provider>
<Routes />
</Auth0Provider>
);
};
6. Setup Auth0 and npm start
That all !
Top comments (2)
With this code, can I use redux for storing the token?
Sorry for late.
I think not need store to redux but it's enable with useEffect
maybe like this: