skip to Main Content

I am trying to build a web app with AWS Amplify. I have authentication configured, but I want certain pages to be for authenticated users only e.g. anyone can see the home page, but only logged in users should see "/dashboard". I am currently using AWS Amplify as my backend and a React front-end, using react-router v6 to route between pages.

Currently, my routing code is very naive (it is my first time using React), and is in App.js:

import React from 'react';
import {
  BrowserRouter,
  Route,
  Routes,
} from 'react-router-dom';

import Login from './pages/Login';
import Home from './pages/Home';
import Dashboard from './pages/Dashboard';
import ErrorPage from './pages/ErrorPage';

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route exact path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="*" element={<ErrorPage />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

I first tried wrapping the page I want gated with withAuthenticator, but this just caused a loop of seeing the login box.

function Dashboard({ signOut, user }) {
  return (
    <>
      <h1>Hello {user.username}, this is still in development.</h1>
      <button onClick={signOut}> Sign out</button>
    </>
  );
}

export default withAuthenticator(Dashboard);

I have also tried to add a function to check if the user is authenticated, and return different bodies, but this just shows a white screen for both an authenticated and non-authenticated users. I believe it is because it is async, but I am not familiar enough with react to understand why or how to fix it.

async function isAuthed() {
  try {
    await Auth.currentAuthenticatedUser();
    return true;
  } catch(e) {
    return false;
  }
}

async function Dashboard() {
  if (await isAuthed()) {
    return (
      <>
        <h1>Hello, this is still in development.</h1>
      </>
    );
  } else {
    return (
      <>
        <h1>Please login to view this page.</h1>
      </>
    )
  }
}

I have also tried to see if there is some method of asynchronous routing, but not sure how I would implement that.

Edit:

@Jlove’s solution has worked as intended, my updated App.js routing code is now:

import React, { useState, useEffect } from 'react';
import {
  BrowserRouter,
  Route,
  Routes,
  useNavigate,
} from 'react-router-dom';
import { Amplify, Auth } from 'aws-amplify'

import Login from './pages/Login';
import Home from './pages/Home';
import Dashboard from './pages/Dashboard';
import ErrorPage from './pages/ErrorPage';
import Unauthenticated from './pages/Unauthenticated';

function RequireAuth({ children }) {
  const navigate = useNavigate();
  const [isAuth, setIsAuth] = useState(null);

  useEffect(() => {
    Auth.currentAuthenticatedUser()
      .then(() => setIsAuth(true))
      .catch(() => {
        navigate("/unauthenticated")
      })
  }, [])
    
  return isAuth && children;
}

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route exact path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />
        <Route
          path="/dashboard" 
          element={
            <RequireAuth>
              <Dashboard />
            </RequireAuth>
          }
        />
        <Route path="*" element={<ErrorPage />} />
        <Route path="/unauthenticated" element={<Unauthenticated />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

2

Answers


  1. Here’s one way to go about this by wrapping your component route in an authorization component:

    <Route 
        path="/somePathToProtect"
        element={
            <RequireAuth>
                <Dashboard />
            </RequireAuth>
        }
    />
    
    export function RequireAuth({children}) {
        const navigate = useNavigate();
    
        const [isAuth, setIsAuth] = useState(null);
    
        useEffect(() => {
            Auth.currentAuthenticatedUser()
                .then(
                    () => setIsAuth(true)
                )
                .catch(() => {
                    navigate('/routeToCatchNonAuth')
                })
        }, [])
    
        return isAuth && children;
    }
    

    The goal here is to gatekeep your route based on what Auth returns. If Auth takes the catch route, utilize the router to navigate the user to wherever you want unauthorized users to go.

    Login or Signup to reply.
  2. You will want to keep separate the logic of protecting routes from the content each route renders. Don’t mix in authentication with the UI/content components you want to render on routes.

    A common protection pattern is to use a layout route to wrap an entire group of routes you want to protect access to. You will create a layout route component that triggers an effect to check the current user’s authentication status, and conditionally return:

    • Nothing/loading (if status isn’t known yet)
    • An Outlet for the protected content (if user is authenticated),
    • Aa redirect to a non-protected route (if user is unauthenticated).

    The prevents (a) accidental access to protected pages prior to knowing a user is unauthenticated and (b) accidental redirects to login prior to knowing a user was really already authenticated.

    Example:

    const checkAuthentication = async () => {
      try {
        await Auth.currentAuthenticatedUser();
        return true;
      } catch {
        return false;
      }
    };
    
    import { Outlet, Navigate } from 'react-router-dom';
    
    const ProtectedRoute = () => {
      const [isAuth, setIsAuth] = useState(undefined);
    
      useEffect(() => {
        checkAuthentication()
          .then(() => setIsAuth(true))
          .catch(() => setIsAuth(false));
      }, []);
    
      if (isAuth === undefined) {
        return null; // or loading spinner/indicator/etc
      }
        
      return isAuth ? <Outlet /> : <Navigate to="/login" replace />;
    }
    

    Wrap the routes that need to be auth-protected.

    import React from 'react';
    import {
      BrowserRouter,
      Route,
      Routes,
    } from 'react-router-dom';
    import Login from './pages/Login';
    import Home from './pages/Home';
    import Dashboard from './pages/Dashboard';
    import ErrorPage from './pages/ErrorPage';
    import ProtectedRoute from './components/ProtectedRoute';
    
    const App = () => {
      return (
        <BrowserRouter>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/login" element={<Login />} />
            <Route element={<ProtectedRoute />}>
              <Route path="/dashboard" element={<Dashboard />} />
              {/* ... other protected routes ... */}
            </Route>
            <Route path="*" element={<ErrorPage />} />
          </Routes>
        </BrowserRouter>
      );
    }
    
    export default App;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search