Using NextAuth in Next Pages Router

When developing my portfolio, I wanted to have a section of the website just for me. An admin side, so to speak. While I have used Firebase before, I wanted to explore new technologies that I have not used before. Since the portfolio is built on top of Next.js, I decided to use NextAuth (v4) for this project.

It’s important to note that this project would be using the /pages directory, instead of /app.

Installation

	npm install next-auth

Add [...nextauth.ts]

In /pages/api, add /auth/[...nextauth.ts]. Here, I initialised and configured NextAuth() itself.

import NextAuth from "next-auth/next";
import GithubProfile from "next-auth/providers/github";

export default NextAuth({
    providers: [
        GithubProfile({
            clientId: process.env.GITHUB_ID as string,
            clientSecret: process.env.GITHUB_SECRET as string,
        }),
    ],
    callbacks: {
        // only allow sign in from my email
        async signIn({ user }) {
            if (user.email == "shawnanuptraamartin@gmail.com") {
                return true;
            } else {
                return false;
            }
        },
    },
    secret: process.env.NEXTAUTH_SECRET as string,
});

I configured NextAuth properties: providers, callbacks, and secret. The providers options is used to configure the different ways to authenticate users. Google, Twitter, Discord, and many more are available. I chose Github because I am a well respected developer, and definitely not because my mentor suggested it.

The callbacks option basically states callback functions that are called after an action was run. I only used the signIn callback as an authorisation tool - ensuring only one email can be used to sign in and access the page. return true to continue the auth flow, return false will bring up the default error page.

While it is not needed in development, the secret option is essential in production as it will not complete the build process without it. It is used to encrypt JWTs and hash email tokens.

The use of process.env variables is used so that I do not expose my keys. It’s never fun to have someone else use your keys.

Implementation

Firstly, we need to wrap the entire app with <SessionProvider> with the prop session.

import Layout from "@/components/layout";
import { GlobalStyles } from "@/components/styles";
import { Session } from "next-auth";
import { SessionProvider } from "next-auth/react";
import type { AppProps } from "next/app";

export default function App(
    { Component, pageProps }: AppProps,
    session: Session
) {
    return (
        <SessionProvider session={session}>
            <Layout>
                <GlobalStyles />
                <Component {...pageProps} />
            </Layout>
        </SessionProvider>
    );
}

The <SessionProvider> allows sessions to be shared between browser tabs. However, it is to note that the session prop will need to be populated later.

Now the fun part: using it in the UI!

import { signIn, signOut, getSession, GetSessionParams } from "next-auth/react";
import { useRouter } from "next/router";

const Login = ({ session }: any) => {
    if (session?.user?.email) {
        return (
            <div>
                Signed in as {session?.user?.email} <br />
                <button onClick={() => signOut()}>Sign out</button>
            </div>
        );
    } else {
        return (
            <div>
                <button onClick={() => signIn()}>Sign in</button>
            </div>
        );
    }
};

export default Login;

export async function getServerSideProps(ctx: GetSessionParams | undefined) {
    const session = await getSession(ctx);
    return { props: { session } };
}

When /admin is first loaded, since there are no sessions, the page will render the Sign in button. After clicking the button and invoking signIn() , the user will be redirected to a sign in page auto generated by NextAuth. After the sign in process, when the user is redirected back to /admin, the async signIn() that was configured in callbacks before was invoked, determining if the current user is able to access the page.

If the user is successfully signed in and redirected to the page, the page will need reload to load new information (a new session object). I mentioned before that the SessionProvider will need to be populated; since the page contains dynamic content (based on user authorisation), we need to implement Server Side Rendering (SSR), or the page won’t work properly. This is why we have export async function getServerSideProps().

By using getServerSideProps(), we are telling Next.js to fetch some data before rendering and sending the HTML back to the browser. In this case, getting the session object. This session object is passed and accessed in the Login() component through the props.

This can also be seen in /admin/dashboard below.

import { getSession, GetSessionParams } from "next-auth/react";
import React from "react";

const Dashboard = ({ session }: any) => {
    return (
        <div>
	          Dashboard
        </div>
    );
};

export default Dashboard;

export async function getServerSideProps(ctx: GetSessionParams | undefined) {
    const session = await getSession(ctx);
    // if no session, redirect to home
    if (!session) {
        return {
            redirect: {
                destination: "/",
                permanent: false,
            },
        };
    }
    return { props: { session } };
}

Note in the above code that there are extra functionalities in getServerSideProps(). This just means that the user will be redirected if the page is accessed before the session object is created.

And now you’re all set! Time to push to production!

Pushing 🅿️(roduction)

Using Vercel as a host makes the process of pushing to production really easy. Just connect your Github account with Vercel, and you can deploy any of your public repository. There are a few things to keep in mind:

  1. Remember to change / add the URL of your deployed site to your auth provider! I used Github for my project, and I created a production-only app since Github only allows one domain and authorised callback url.
  2. When deploying in Vercel, your main domain name is https://www.your-domain.com. Please take note of the www!!! It took me 3 days of work to figure this out. Don’t make the same mistake as I did and make sure all your authorised callback urls contains the www.

Conclusion

We have gone through the process of implementing authentication and authorisation with NextAuth with Next.js. The process is relatively straightforward (at least with Github), as long as the correct configuration steps are followed.

Now you can go and make your own authenticated websites! Goodluck and have fun 🥳🥳

P.S.

It’s very annoying to use NextAuth in preview branches in Vercel. The build constantly fails, and I have no intentions of using other methods to authenticate. It might just be an OAuth thing instead, though.