Following a 301 Redirect from an API in a React App

Viewed 32

I've written a custom ProtectedRoute component for my React app that redirects a user to a /api/login route on my Express API if that user is not authenticated. The /api/login route returns a 301 Redirect to the Auth0 Universal Login UI.

I know the /api/login route on the Express API works because I can hit it directly I get redirected to the Auth0 Universal Login (see the last code snippet).

I also know the ProtectedRoute is redirecting correctly because it redirects to localhost:3000/api/login, which is the correct route on the Express API to trigger the Auth0 Universal Login redirect. What actually happens though is that localhost:3000/api/login shows up in the address bar but the redirect to the Auth0 Universal Login doesn't happen. That being said if I refresh the page then the redirect to the Universal Login UI works.

I'm not exactly sure why the Redirect returned from /api/login isn't followed in the Browser. I think it has something to do with how React is navigating to the route.

Here's the relevant code snippets. If more are needed let me know.

Protected Route Component

import { Navigate, useLocation } from 'react-router-dom';

interface ISession {
    userId: string;
    role: string;
    details: any;
}

type RouteProps = {
    children?: JSX.Element;
    session: ISession;
    loading: boolean;
};

const ProtectedRoute = ({ session, children, loading }: RouteProps) => {
  const location = useLocation();

    if (loading) return null;
    else if (!!session.userId) {
      return children ? children : <Outlet />;
  }
    else {
      return <Navigate replace to="/api/login" state={{ redirectTo: location.pathname }} />;
  }
};

export default ProtectedRoute;

How the ProtectedRoute Component is used with React Router

import { useContext } from 'react';
import { Route, Routes } from 'react-router-dom';
import Home from './pages/home';
import 'bootstrap/dist/js/bootstrap.bundle.min';
import { SessionContext } from './context/SessionContext';
import ProtectedRoute from './middleware/protectedRoute';
const App = () => {
  const { session, loading } = useContext(SessionContext);

  console.log('Session', session);
  return (
    <Routes>
      <Route element={<ProtectedRoute loading={loading} session={session} />}>
        <Route path="/" element={<Home />} />
        <Route path="/dne" element={ <p>Stuff</p> } />
      </Route>
    </Routes>
  );
};

export default App;

NOTE: I'm excluding the code from the SessionContext component for brevity. Since the user isn't able to login because the redirect to the Auth0 Universal Login UI doesn't work no session is ever created.

The "/api/login" route handler on the Express API

const login = (req: Request, res: Response) => {
  const { redirectTo } = req.query;
  const domain = config.get('auth0.domain');
  const clientId = config.get('auth0.clientId');
  const host = config.get('host');


  // These 301 Responses are the Redirect to Auth0's Universal Login UI
  if (redirectTo) {
    const encodedRedirect = base64.urlEncode(redirectTo as string); // A Custom Base64 encoder that is URL Safe
    res.status(301).redirect(`${domain}/auth/authorize?response_type=code&scope=openid&client_id=${clientId}&redirect_uri=${host}/api/auth/callback&state=${encodedRedirect}`)
  } else {
    res.status(301).redirect(`${domain}/auth/authorize?response_type=code&scope=openid&client_id=${clientId}&redirect_uri=${host}/api/auth/callback`);
  }
};
1 Answers

It's not really an answer for what is going on but it is a solution.

The problem was <Navigate to="/api/login" /> would cause React to Rerender the page and would change the URL in the address bar, but it would not cause the browser to make a GET request to the new address.

To solve this I just overwrote the window.location.href with /api/login in the ProtectedRoute component.

Here's the new version of the ProtectedRoute component.

const ProtectedRoute = ({ session, children, loading }: RouteProps) => {
  const location = useLocation();

  if (loading)
    return null;

  if (!!session.userId) {
    return children ? children : <Outlet />;
  }
  else {
    window.location.href = '/api/login';
    return <Navigate replace={true} to='/api/login' />
  }
};
Related