Authentication with ReactJs, Express and Auth0: Part 2 - Logout, useCheckAuth hook and withAuth HOC
In the previous posts, I managed to create an Authentication flow using Auth0, Express, and ReactJS. We stopped at the point of successfully allow the user to log in and display the user profile. In case you have missed it, this is part 1 of this post
There're several things left to cover in this post including:
- Logging user out
- Create a useful React Hook to validate actions
- And create a HOC component for stopping unauthorized users
Implement logout flow
Allow users to sign out is important but easy to implement. We'll need to
Log user out of Auth0
Remove cached user-profiles and auth
id_token
For the first step, when users click logging out, let's send them to Auth0 logout URL
// client/src/components/urls.js
const createLogoutUrl = () => {
return `${ssoDomain}/v2/logout?client_id=${clientId}&returnTo=${redirectUriLogout}`;
};
export const logoutUrl = createLogoutUrl();
The logoutUrl
looks like this https://rickandmorty-wiki.us.auth0.com/v2/logout?client_id=Wb5VJ5rB6DKAwlWfI2GNw6prRx82BxHr&returnTo=http://localhost:8080/logout
The important part is redirectUriLogout
which is our server-side callback address. When visited, this path will clear the cookie, session, etc.
In our case, since we don't use any session, let's just redirect the user back to the client callback page
// src/routes/logout.js
const router = express.Router();
router.get("/", (req, res) => {
res.redirect(`${clientUrl}/logout`);
});
export default router;
And in client code, we'll take this chance to clean up data in localStorage
and then redirect the user back to the homepage
// client/src/routes/logout/LogoutPage.js
import React from "react";
import { Redirect } from "@reach/router";
import {
LocalStorageManager,
LS_AUTH_KEY,
} from "../../managers/LocalStorageManager";
const LogoutPage = () => {
LocalStorageManager.remove(LS_AUTH_KEY);
return <Redirect to="/" noThrow />;
};
export default LogoutPage;
And we're done. Much easier to implement compared to login/ register flow, right?
And for this last section of this post, we'll implement some useful components to check and authenticate the user
Use HOC to prevent end-user to access a private page
Since our /bookmarks
page is only available for authenticated users to access, let's show a message whenever unauthorized users try to access the page and show them a link to direct the user to the login page
This kind of logic is called cross-cutting-concern and will appear in many other places in our application. It makes sense to encapsulate the code using React' HOC High order function:
The first set of parenthesis is the first parameter of the function, this should be an options object for our HOC function, but in this case, we'll leave it empty
The second set of parenthesis is the return value of the first function. It takes the input of the actual component we're decorating and return a React component in the form of a function
The third st of parenthesis contains the actual logic of our HOC. It will make use of the
useAuth
hook, check for if user data is available, and show a message if the user is not logged in
This is the complete code
// client/src/components/AuthContext/withAuth.js
export const withAuth = () => (WrappedComponent) => (props) => {
const { user, isLoading, error } = useAuth();
if (isLoading || error) {
return handleLoadingAndError({ isLoading, error });
}
if (!user) {
return (
<Container maxWidth="sm">
<Alert severity="warning" variant="outlined">
<AlertTitle>Error</AlertTitle>
You must{" "}
<Link component="a" href={loginUrl}>
login
</Link>{" "}
to access this page
</Alert>
</Container>
);
}
return <WrappedComponent {...props} />;
};
Using this HOC function is relatively easy
// client/src/routes/bookmarks/BookmarksPage.js
const BookmarksPage = () => {
// ...
};
export default withAuth()(BookmarksPage);
User ReactHook to validate action before submit
Remember the Bookmark buttons on the homepage? We also need to validate to check if the user is logged in or not. In case the user is not logged in, we can show a friendly message to remind users that they need to login and not executing the action
Following the logic in the previous section, since we want to reuse the code here for other places in our system, it's beneficial to encapsulate all the logic inside a React hook function
This is the final form of the BookmarkButton, demonstrating how we're using our hook:
// client/src/components/CharacterList/BookmarkButton.js
const BookmarkButton = ({ characterId }) => {
const { withAuth } = useCheckAuth();
const onClick = () => {
// make server call to add bookmark
};
return (
<IconButton onClick={withAuth(onClick)}>
<Bookmark color="disabled" />
</IconButton>
);
};
useCheckAuth
is a react hook that will return withAuth
function. This function accepts an action as input and returns a composed action that checks for user authentication when invoked.
This is the complete code of useCheckAuth
function
// client/src/components/AuthContext/useCheckAuth.js
const useCheckAuth = () => {
const { user, isLoading, error } = useAuth();
const withAuth = (action) => () => {
if (!isLoading)
if (error) {
return toast.error(error.message);
} else if (!user) {
return toast.warn(
<Typography variant="body2">
You must{" "}
<Link component="a" href={loginUrl}>
login
</Link>{" "}
to perform this action
</Typography>
);
} else {
return action();
}
};
return { withAuth };
};
export default useCheckAuth;
As you can see, the hook makes use of our context to retrieve user
info and show a message if the user is not logged in
Conclusion
So as you can see it's very easy to implement authentication flow using Auth0 and ReactJS. In part 1 and part 2, we have:
Create a new application and implement login and logout flow using ReactJS, Express, and Auth0
Implement AuthContext for providing all pages with user info
Implement hooks for checking and preventing authenticated users to perform certain actions
Implement HOC for denying unauthenticated users from accessing private pages
Again, this is the complete source code hosted in Github: Github and the demo application Rick and Morty wiki
In the next posts, we'll replace Auth0 with our self-hosted KeyCloak server. See you later!