How do you deal with public and private routes in a NextJS app?

Viewed 26005

I'm working on a app that we have public and admin routes, in our past CRA app we've used custom routes elements, but we dont have thins in nextjs... We have a lot of public pages, and we have 20 private pages/routes.

Whats is the best way to deal with protected autenticated routes and public routes in nextjs?

Thanks so much! Best

5 Answers

I personally have been using HOCs (higher-order component) for this.

Here is an example authentication HOC:

const withAuth = Component => {
  const Auth = (props) => {
    // Login data added to props via redux-store (or use react context for example)
    const { isLoggedIn } = props;

    // If user is not logged in, return login component
    if (!isLoggedIn) {
      return (
        <Login />
      );
    }

    // If user is logged in, return original component
    return (
      <Component {...props} />
    );
  };

  // Copy getInitial props so it will run as well
  if (Component.getInitialProps) {
    Auth.getInitialProps = Component.getInitialProps;
  }

  return Auth;
};

export default withAuth;

You can use this HOC for any page component. Here is an example of usage:

const MyPage = () => (
  <> My private page</>
);

export default withAuth(MyPage);

You can extend withAuth HOC with role checks like public, regular user and admin if needed.

I think it depends on the type of page.

For statically generated page:

You can use a HOC for authentication like what @AleXius suggested.

For a server-side rendered page:

You can perform your authentication logic in getServerSideProps.

export async function getServerSideProps(context) {
  const sendRedirectLocation = (location) => {
    res.writeHead(302, {
      Location: location,
    });
    res.end();
    return { props: {} }; // stop execution
  };

  // some auth logic here
  const isAuth = await authService('some_type_of_token')

  if (!isAuth) {
    sendRedirectLocation('/login')
  }

  return {
    props: {}, // will be passed to the page component as props
  }
}

For a server-side rendered page with a Custom Server:

Depends on your choice of server, you can choose different solutions. For Express, you can probably use an auth middleware. If you prefer, you can also handle it in getServerSideProps.

Thanks a lot @AleXiuS for your answer ! I have mixed your solution and this great article for those who want to use this hoc with typescript :

import { NextComponentType } from "next";

function withAuth<T>(Component: NextComponentType<T>) {
  const Auth = (props: T) => {
    // Login data added to props via redux-store (or use react context for example)
    const { isLoggedIn } = props;

    // If user is not logged in, return login component
    if (!isLoggedIn) {
      return <Login />;
    }

    // If user is logged in, return original component
    return <Component {...props} />;
  };

  // Copy getInitial props so it will run as well
  if (Component.getInitialProps) {
    Auth.getInitialProps = Component.getInitialProps;
  }

  return Auth;
}

export default withAuth;

in additional to the solution of using HOC you can use the ssr methods from next like getServerSideProps, in the case you'll have to modify your signIn function to set a header into your requisitions(this header will say if you're logged or not) something like this:

const signIng = async() =>{
...
    api.defaults.headers.someTokenName = token; //Here you can set something just to identify that there is something into someTokenName or your JWT token
...
}

Then in yout withAuth component:

const WithAuth = (): JSX.Element => {
  // ... your component code
}

export const getServerSideProps: GetServerSideProps = async(ctx) => {
  const session = await ctx.req.headers['someTokenName'];

 if(!session){
   return{
    redirect:{
      destination: '/yourhomepage', //usually the login page
      permanent: false,
    }
   }
 }

 return{
  props: {
   authenticated: true 
  }
 }
}

This should prevent your web application from flickering from unauthenticated to authenticated

This is a typescript version, you can pass the allowed permission to HOC and compare it with the existing permissions of the logged-in user.

export interface ProtectedComponentProps {
requiredPermission: string;
}

const ProtectedComponent: React.FC<ProtectedComponentProps> = (props) => {
const [isAuthorized, setIsAuthorized] = useState<boolean>();
useEffect(() => {
    const permissions = authService.getPermissions();
    setIsAuthorized(permissions.includes(props.requiredPermission))

}, []);
return (
    <>
        {isAuthorized ? props.children : <p>not authorized</p>}
    </>


);
}

export default ProtectedComponent;

and use it like this :

 <ProtectedComponent requiredPermission="permissionName">
      <SomeProtectedComponent />
 </ProtectedComponent>
Related