- Protecting a route in a
react-router-dom
app - Protecting a route in a Gatsby app
- Protecting a route in a Next.js app (in SPA mode)
- Create a
useApi
hook for accessing protected APIs with an access token.
So that we can access the router history
outside of the Router
component you need to create your own history object. We can reference this object from the Auth0Provider
's onRedirectCallback
.
We can then use the withAuthenticationRequired
HOC (Higher Order Component) to create a ProtectedRoute
component that redirects anonymous users to the login page, before returning them to the protected route:
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import { Auth0Provider, withAuthenticationRequired } from '@auth0/auth0-react';
import { createBrowserHistory } from 'history';
import Profile from './Profile';
export const history = createBrowserHistory();
const ProtectedRoute = ({ component, ...args }) => (
<Route component={withAuthenticationRequired(component)} {...args} />
);
const onRedirectCallback = (appState) => {
// Use the router's history module to replace the url
history.replace(appState?.returnTo || window.location.pathname);
};
export default function App() {
return (
<Auth0Provider
domain="YOUR_AUTH0_DOMAIN"
clientId="YOUR_AUTH0_CLIENT_ID"
redirectUri={window.location.origin}
onRedirectCallback={onRedirectCallback}
>
{/* Don't forget to add the history to your router */}
<Router history={history}>
<Switch>
<Route path="/" exact />
<ProtectedRoute path="/profile" component={Profile} />
</Switch>
</Router>
</Auth0Provider>
);
}
Wrap the root element in your Auth0Provider
to configure the SDK and setup the context for the useAuth0
hook.
The onRedirectCallback
will use gatsby
's navigate
function to return the user to the protected route after the login:
// gatsby-browser.js
import React from 'react';
import { Auth0Provider } from '@auth0/auth0-react';
import { navigate } from 'gatsby';
const onRedirectCallback = (appState) => {
// Use Gatsby's navigate method to replace the url
navigate(appState?.returnTo || '/', { replace: true });
};
export const wrapRootElement = ({ element }) => {
return (
<Auth0Provider
domain="YOUR_AUTH0_DOMAIN"
clientId="YOUR_AUTH0_CLIENT_ID"
redirectUri={window.location.origin}
onRedirectCallback={onRedirectCallback}
>
{element}
</Auth0Provider>
);
};
Create a page that you want to be protected, e.g. a profile page, and wrap it in the withAuthenticationRequired
HOC:
// src/pages/profile.js
import React from 'react';
import { useAuth0, withAuthenticationRequired } from '@auth0/auth0-react';
const Profile = () => {
const { user } = useAuth0();
return (
<ul>
<li>Name: {user.nickname}</li>
<li>E-mail: {user.email}</li>
</ul>
);
};
// Wrap the component in the withAuthenticationRequired handler
export default withAuthenticationRequired(Profile);
Wrap the root element in your Auth0Provider
to configure the SDK and setup the context for the useAuth0
hook.
The onRedirectCallback
will use next
's Router.replace
function to return the user to the protected route after the login:
// pages/_app.js
import React from 'react';
import App from 'next/app';
import Router from 'next/router';
import { Auth0Provider } from '@auth0/auth0-react';
const onRedirectCallback = (appState) => {
// Use Next.js's Router.replace method to replace the url
Router.replace(appState?.returnTo || '/');
};
class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<Auth0Provider
domain="YOUR_AUTH0_DOMAIN"
clientId="YOUR_AUTH0_CLIENT_ID"
redirectUri={typeof window !== 'undefined' && window.location.origin}
onRedirectCallback={onRedirectCallback}
>
<Component {...pageProps} />
</Auth0Provider>
);
}
}
export default MyApp;
Create a page that you want to be protected, e.g. a profile page, and wrap it in the withAuthenticationRequired
HOC:
// pages/profile.js
import React from 'react';
import { useAuth0, withAuthenticationRequired } from '@auth0/auth0-react';
const Profile = () => {
const { user } = useAuth0();
return (
<ul>
<li>Name: {user.nickname}</li>
<li>E-mail: {user.email}</li>
</ul>
);
};
// Wrap the component in the withAuthenticationRequired handler
export default withAuthenticationRequired(Profile);
// use-api.js
import { useEffect, useState } from 'react';
import { useAuth0 } from '@auth0/auth0-react';
export const useApi = (url, options = {}) => {
const { getAccessTokenSilently } = useAuth0();
const [state, setState] = useState({
error: null,
loading: true,
data: null,
});
const [refreshIndex, setRefreshIndex] = useState(0);
useEffect(() => {
(async () => {
try {
const { audience, scope, ...fetchOptions } = options;
const accessToken = await getAccessTokenSilently({ audience, scope });
const res = await fetch(url, {
...fetchOptions,
headers: {
...fetchOptions.headers,
// Add the Authorization header to the existing headers
Authorization: `Bearer ${accessToken}`,
},
});
setState({
...state,
data: await res.json(),
error: null,
loading: false,
});
} catch (error) {
setState({
...state,
error,
loading: false,
});
}
})();
}, [refreshIndex]);
return {
...state,
refresh: () => setRefreshIndex(refreshIndex + 1),
};
};
Then use it for accessing protected APIs from your components:
// users.js
import { useApi } from './use-api';
export const Profile = () => {
const opts = {
audience: 'https://api.example.com/',
scope: 'read:users',
};
const { login, getTokenWithPopup } = useAuth0();
const { loading, error, refresh, data: users } = useApi(
'https://api.example.com/users',
opts
);
const getTokenAndTryAgain = async () => {
await getTokenWithPopup(opts);
refresh();
};
if (loading) {
return <div>Loading...</div>;
}
if (error) {
if (error.error === 'login_required') {
return <button onClick={() => login(opts)}>Login</button>;
}
if (error.error === 'consent_required') {
return (
<button onClick={getTokenAndTryAgain}>Consent to reading users</button>
);
}
return <div>Oops {error.message}</div>;
}
return (
<ul>
{users.map((user, index) => {
return <li key={index}>{user}</li>;
})}
</ul>
);
};