DEV Community

terrierscript
terrierscript

Posted on

Rewrite Auth0 Example with React Hooks

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)

Collapse
 
bouncydragon profile image
Duane Cary

With this code, can I use redux for storing the token?

Collapse
 
terrierscript profile image
terrierscript

Sorry for late.

I think not need store to redux but it's enable with useEffect

maybe like this:

const TokenSync => ({updateTokenAction]){ const { token } = useAuth0Context() // append token  useEffect( () => updateTokenAction(token), [token]) return null } // usage: <TokenSync />