Authentication with credentials using Next-Auth and MongoDB - Part 2
Mainak Das
Posted on July 15, 2021
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 whether 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 ofuseSession()
to share the session object across components, by usingReact 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.
Posted on July 15, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.