How to Create Custom Login Pages with NextAuth


I’ve been considering adding authentication into my Next.js application. While OAuth integration is fairly simple, I noticed a massive community backing for next-auth, which supports many built-in OAuth providers and even passwordless authentication.

Getting it up and running with protected pages only took a few hours.

The only thing that took a little more effort was creating a custom login page (I just wanted nice, colorful buttons for each provider).

Disclaimer: I’m going to assume you’ve gone through the documentation for setting up basic OAuth using next-auth’s built-in login page. Also, the code snippets in this article require NextAuth.js v4. Check out how to upgrade to version 4.

Solution from Documentation

The next-auth configuration documentation states to use the pages option in pages/api/auth/[...nextauth].js to define a custom login page.

// pages/api/auth/[...nextauth].js
pages: {
  signIn: '/signin',
  ...
}

We can then have our custom, branded login page that pulls the supported providers from [...nextauth].js.

// pages/signin.jsx
import { getProviders, signIn } from "next-auth/react";
export default function SignIn({ providers }) {
  return (
    <>
      {Object.values(providers).map((provider) => (
        <div key={provider.name}>
          <button onClick={() => signIn(provider.id)}>
            Sign in with {provider.name}
          </button>
        </div>
      ))}
    </>
  );
}
export async function getServerSideProps(context) {
  return { props: { providers: await getProviders() } };
}

Finally, next-auth will redirect to our custom page when we make a call to /api/auth/signin.

// path/to/someComponent.jsx
<Link legacyBehavior href="/api/auth/signin">Sign in</Link>

This works as expected, but the URL ends up looking something like this:

http://localhost:3000/signin?callbackUrl=http://localhost:3000

The callbackUrl is explicitly defined in the query string, which might not be desired in many use cases.

Alternative Solution

I found that we don’t need to route through next-auth for our custom /signin page.

We can fully remove our default /signin page from [...nextauth].js.

// pages/api/auth/[...nextauth].js
pages: {
  // signIn: '/signin',
  ...
}

And then redirect directly to this page from another component.

// path/to/someComponent.jsx
<Link legacyBehavior href="/signin">Sign in</Link>

The signIn() and even signOut() functions will still work as expected.

I would recommended explicitly setting the callbackUrl for these function calls.