23

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

Wilson Neto
  • 353
  • 1
  • 3
  • 6

6 Answers6

44

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.

AleXiuS
  • 694
  • 7
  • 12
  • This method is importing my login page styles to every other component. – Alcides Bezerra Jan 17 '21 at 15:12
  • 1
    Probably because "isLoggedIn" boolean is true before turning into false? You'd also see a flash of login page in that case... If you authenticate in client side, you could add "loading" prop and check that it is not in loading state as well. – AleXiuS Jan 18 '21 at 16:23
  • Do you have typescrip example? – Aseer KT Miqdad Sep 23 '21 at 08:13
  • What about URL the address bar shows the component path but shows Login you need to redirect – Muhammed Ozdogan Oct 06 '21 at 12:48
  • 3
    This does not work. – Antonio Pavicevac-Ortiz Nov 28 '21 at 14:47
  • But what if I have different layouts for auth page and private? – Disorder Jun 17 '22 at 20:06
  • instead of importing the login components, you can just redirect to the login page route, and after login, you can redirect the client to his last visited route so that the client can log in and get back to the same page where he was! doing that improves your UX, and you don't import the login component into every private route you have. – Lazybob Oct 01 '22 at 16:20
5

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;
Putxe
  • 1,054
  • 1
  • 16
  • 23
3

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.

Andrew Zheng
  • 2,612
  • 1
  • 20
  • 25
1

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

1

Here is an example of how you can create a private route in Next.js:

First, create a higher-order component (HOC) that will check if the user is authenticated before rendering the protected route component:

import { useEffect, useState } from 'react';
import Router from 'next/router';

const withAuth = (WrappedComponent) => {
const WithAuth = (props) => {
    const [loading, setLoading] = useState(true);
    const [user, setUser] = useState(null);

    useEffect(() => {
        // Fetch user data here and set it using setUser
        // For example:
        // setUser(fetchUserData());

        setLoading(false);
    }, []);

    if (loading) {
        return <div>Loading...</div>;
    }

    if (!user) {
        Router.push('/login');
        return null;
    }

    return <WrappedComponent {...props} />;
};

WithAuth.getInitialProps = async (ctx) => {
    const wrappedComponentInitialProps = WrappedComponent.getInitialProps
        ? await WrappedComponent.getInitialProps(ctx)
        : {};

       return { ...wrappedComponentInitialProps };
    };

   return WithAuth;
};

export default withAuth;

Then, use the withAuth HOC to wrap the protected route component:

import withAuth from '../hoc/withAuth';

const ProtectedRoute = (props) => {
  return <div>This is a protected route</div>;
};

export default withAuth(ProtectedRoute);

Finally, use the protected route component in your Next.js app as you would any other route:

import ProtectedRoute from '../components/ProtectedRoute';

const Home = () => {
  return (
    <div>
      <ProtectedRoute />
    </div>
  );
};

export default Home;

This is just one way to implement private routes in Next.js, but it should give you an idea of how it can be done.

Omar Faruq
  • 41
  • 6
0

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>
Cyber Progs
  • 3,656
  • 3
  • 30
  • 39