Implementing a custom Okta authentication error page in ReactJS

Updated May 5, 2020 to include Okta React library 3.0

The @okta/okta-react library is fantastic. It makes working with Okta authentication really easy in ReactJS. It helps streamline managing the user’s authentication including their tokens. You don’t even have to create a log in component on your app, users can be redirected to Okta to log in and then they return automatically. Love it!

We came across a use case where every once in a while, users would get generic error messages from Okta in our app, something like:

Note, that error message above is clear, this is how I can recreate the issue. The actual error message users were getting was a wee bit more vague and pertaining to invalid tokens.

We really didn’t like this error message for a few reasons:

  • It’s ugly, plain text, doesn’t fit our brand, doesn’t give users the feel goods. Every page in your app should be part of your app, even error pages.
  • There is no way to encourage users to take an action to rectify the issue. Just showing this blank page doesn’t help anyone.
  • The error message is confusing for end users who aren’t technical. Any messaging we give users should be meaningful to them.

How to customize the authentication callback page

First, a quick overview on how we are using the okta library.

As mentioned, we are using the @okta/okta-react library to handle our authentication and securing our routes, in App.js:

<SecureRoute exact path={CONFIG.UI_URL.HOME} component={HomeContainer} />
<SecureRoute path={CONFIG.UI_URL.PEOPLE} component={PeopleContainer} />
<SecureRoute path={CONFIG.UI_URL.MATERIAL} component={MaterialContainer} />
<Route path={CONFIG.UI_URL.ERROR(':errorCode')} component={ErrorContainer} />
<Route path={CONFIG.OKTA.REDIRECT_URL} component={ImplicitCallback} />
<Redirect to={CONFIG.UI_URL.ERROR('404')} />

The second to last Route handles the call back from Okta. The component it calls, ImplicitCallback, initially came from the @okta/okta-react library.

Instead of using ImplicitCallback I wanted to use our own component, and call Okta’s resources when needed.

I created a new component in our solution, called it ImplicitCallback and just replaced the import reference in our App.js file.

I started this endeavor using v1.4 of the library, then we upgraded to v2, then v3. The code differs greatly between them, each version it gets easier and easier. If you’re starting fresh, aim for v3 for sure.

@okta/okta-react v1.x

The following worked on v1.4.1.

Here’s what our ImplicitCallback file contains:

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { withAuth } from '@okta/okta-react'; // we're not using their ImplicitCallback at all, instead handling it all here
import { Redirect } from 'react-router';

import { Layout } from 'containers/Layout/Layout';
import CONFIG from 'config';

export const ImplicitCallback = ({ auth }) => {
  const [authenticated, setAuthenticated] = useState(null);
  const [error, setError] = useState('');
  const [toUrl, setToUrl] = useState('');

  useEffect(() => {
    auth.handleAuthentication() // handles authentication and sets local storage
      .then(() => {
        setToUrl(auth.getFromUri()); // get the URL the user was heading to
        setAuthenticated(true);
      })
      .catch((err) => {
        setError(err.toString());
        setAuthenticated(false);
      });
  }, [auth]);

  const loggingInMessage = <>logging in...</>;

  // being explicit with the following authenticated checks.
  // !authenticated is true if it's null, but we don't want to render anything if it's null, only when true or false
  return (
    <Layout id="implicitcallback" justLayout> // layout has some of our basic branding on it
      <>
        {authenticated === null && // this will show a basic message as the page is processing the response from okta
          loggingInMessage
        }
        {authenticated === true && // user is authenticated, send them to their target page
          <>
            <Redirect to={toUrl} />
            {loggingInMessage}
          </>
        }
        {authenticated === false && // user is not authenticated, send them to our existing error page with the error message
          <Redirect to={CONFIG.UI_URL.ERROR(`401/${error}`)} />
        }
      </>
    </Layout>
  );
};

ImplicitCallback.propTypes = {
  auth: PropTypes.shape().isRequired
};

export default withAuth(ImplicitCallback);

@okta/okta-react v2.x

The following is simpler and works best for v2.x.

import React, { useEffect } from 'react';
import { Redirect } from 'react-router';
import { useOktaAuth } from '@okta/okta-react';
import CONFIG from 'config';

const ImplicitCallback = () => {
  const { authService, authState } = useOktaAuth();

  useEffect(() => {
    authService.handleAuthentication(); // this handles redirects and all
  }, [authService]);

  if (authState.error) {
    return <Redirect to={CONFIG.UI_URL.ERROR(`401/${authState.error}`)} />;
  }

  return null;
};

export default ImplicitCallback;

@okta/okta-react v3.x

Even simpler! Their new LoginCallback component can receive an errorComponent, so when it errors, it can render what you want. In my case, we had a pretty error page already so I just sent it a Redirect to go to the error page.

I did have to make a new component for this to work, but it’s super lightweight:

ErrorRedirect.js

import React from 'react';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router';
import CONFIG from 'config';

const ErrorRedirect = ({ error }) => (<Redirect to={CONFIG.UI_URL.ERROR(`401/${error.toString()}`)} />);

ErrorRedirect.propTypes = {
  error: PropTypes.shape()
};

ErrorRedirect.defaultProps = {
  error: null
};

export default ErrorRedirect;

Then we import this into my existing ImplicitCallback.js file, which now looks incredibly simple:

import React from 'react';
import { LoginCallback } from '@okta/okta-react';
import ErrorRedirect from './ErrorRedirect';

const ImplicitCallback = () => (<LoginCallback errorComponent={ErrorRedirect} />);

export default ImplicitCallback;

Now the clean error page

Looks easy enough! The redirect to our error page is much more beautiful than the okta error page:

We can redirect the user to any page we want! We can now tell them something a little more meaningful, and more importantly give them something to do to try to fix it. Also, since we now “own” this page, we can add our own analytics and track how often users are visiting this page.

Upon clicking Refresh we clear anything Okta related from local storage in the browser, and redirect them to the home page. When they land home our secure route kicks in and sends them back to Okta’s login to revalidate. This for the most part resolves the issue.

If they come back within a minute to this page, we show a different error message encouraging them to try another browser or contact support.

It’s always about the users’ experience

This might seem minor and depending on the size of your app, may or may not seem worth it. Your users’ experience in your app is really important, especially when bad things happen. We do tend to focus on the happy path primarily, and that is a good practice, but we can’t forget about the sad paths. Our users are going to get frustrated if an error occurs, and if the app then simply shows them nonsense in a plain page, that can elevate some users’ frustration quickly.

We all know errors are going to happen, let’s help our users get through them meaningfully.

Leave a Reply

Up ↑

%d bloggers like this: