Authentication with credentials using Next-Auth and MongoDB - Part 2

In the last part, I created the signup, login and route along with the and the connection of frontend to the backend. Also, I created the sign-in logic using next-auth.

In this part, I'll mainly focus on the frontend connection using next-auth.

Posting sign in logic

The next-auth client gives us both signIn() and signOut() hooks that will make our coding a whole lot easier and our only work is to provide the authentication type we will use to sign-in (in our case i.e credentials).

Using the signIn() method ensures the user ends back on the page they started on after completing a sign-in flow. It will also handle CSRF Tokens for you automatically when signing in with email.

The sign-in hook will always return a Promise containing an error key:value pair that will tell us if the authentication is successful or not.

You can look into more detail here.

import { signIn } from 'next-auth/client';
//...
const status = await signIn('credentials', {
                redirect: false,
                email: email,
                password: password,
            });
            console.log(status);

And that's our sign-in logic in place.

But wait, it's not all

Suppose, you're signed in but trying to access the route .../auth which normally shows us the sign-in or sign-up form.

To protect that route, next-auth also gives us a getSession() hook to check for the session and determine wheatear a user is signed in or not.

import { getSession } from 'next-auth/client';

NextAuth.js provides a getSession() method which can be called a client or server-side to return a session.

It calls /api/auth/session and returns a promise with a session object, or null if no session exists.

More info here

Now, let's add this to our .../auth route:

We will use useEffect() and useState() hooks to tell user that something is loading. As getSession() returns a promise we need a then chain for getting the session object. If there is a session we will use next/router to redirect the users to / page.

//...
const [loading, setLoading] = useState(true);
    const router = useRouter();
    useEffect(() => {
        getSession().then((session) => {
            if (session) {
                router.replace('/');
            } else {
                setLoading(false);
            }
        });
    }, []);
    if (loading) {
        return <p>Loading...</p>;
    }
//...

Protect a secured route

On the change password page, we need an authenticated user to do the action, and if any unauthenticated user visits ../profile they will be redirected to the auth page for sign-in or sign-up.

The getSession() hook can also be used on the server to check for sessions and do any redirect based on that.

We will use the hook along with getServerSideProps for checking the session of the user trying to access.

NOTE

When calling getSession() server side, you need to pass {req} or context object.

For securing .../profile page:

export async function getServerSideProps(context) {
    const session = await getSession({ req: context.req });
    if (!session) {
        return {
            redirect: {
                destination: '/auth',
                permanent: false,
            },
        };
    }
    return {
        props: { session },
    };
}

With all the sign-in and sign-up logic in place, now we will look into the Header for showing and hiding the tabs based on user sign-in or not. And finally the sign-out logic.

Dynamic navbar tabs

The useSession hook from next-auth is the best way to check for an authenticated user. The hook gives us a session and loading state that will be updated based on fetching the users' session.

import { useSession } from 'next-auth/client';

We will use the session to show and hide the tabs.

function MainNavigation() {
    const [session, loading] = useSession();
    return (
        <header className={classes.header}>
            <Link href='/'>
                <a>
                    <div className={classes.logo}>Next Auth</div>
                </a>
            </Link>
            <nav>
                <ul>
                    {!session && !loading && (
                        <li>
                            <Link href='/auth'>Login</Link>
                        </li>
                    )}
                    {session && (
                        <li>
                            <Link href='/profile'>Profile</Link>
                        </li>
                    )}
                    {session && (
                        <li>
                            <button >Logout</button>
                        </li>
                    )}
                </ul>
            </nav>
        </header>
    );
}

export default MainNavigation;

After noticing a bit we will see there's a flickering in the navbar tabs. That's because it's checking for the session twice. Next-auth has a workaround for this also. It provides a <Provider> component that shares the session object across multiple components and as a result, useSession doesn't have to check for session twice.

import { Provider } from 'next-auth/client';

Using the supplied React <Provider> allows instances of useSession() to share the session object across components, by using React Context under the hood.

This improves performance, reduces network calls, and avoids page flicker when rendering. It is highly recommended and can be easily added to all pages in Next.js apps by using pages/_app.js.

We can pass the session object to the <Provider> component as a prop to avoid checking twice.

If you pass the session page prop to the <Provider> you can avoid checking the session twice on pages that support both server and client-side rendering.

Let's add this to our _app.js:

<Provider session={pageProps.session}>
    <Layout>
      <Component {...pageProps} />
    </Layout>
</Provider>

Now, the header will no longer flicker.

Let's check the sign-out logic.

Sign Out

Next-auth also gives us a signOut() hook that we can attach with any element onClick() prop and it will just sign us out. It's as easy as that.

More info here.

<li>
    <button onClick={signOut}>Logout</button>
</li>

And that's how we implement authentication with credentials in Next.js.

15