Authentication with ReactJs, Express and Auth0: Part 2 - Logout, useCheckAuth hook and withAuth HOC

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

_Users_Work_Documents_obsidian_home_posts_images_2021-09-20_21-29-41 1.png

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

_Users_Work_Documents_obsidian_home_posts_images_2021-09-20_21-29-58 (1).gif

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!