This page will show you how to set up basic authentication using NextAuth while using custom sign in and out pages. We will use just GitHub for this simple demo, no email / password.
Initial Setup
Quickly download and setup the latest Next.js TypeScript starter:
npx create-next-app@latest --ts .
If you are getting warnings in your CSS file complaining about unknown CSS rules, follow these steps here
Still in globals.css, update the code with this reset from Josh Comeau
// src/app/layout.tsx
import { Inter } from "next/font/google";
import type { Metadata } from "next";
import type { ReactNode } from "react";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
description: "Generated by create next app",
title: "NextAuth Demo"
};
type RootLayoutProps = {
children: ReactNode;
};
const RootLayout = ({ children }: RootLayoutProps) => {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
};
export default RootLayout;
Install the NextAuth package:
npm i next-auth
Auth Route Handler
For this demo, we won’t be using a database
Additionally, we’ll be using the default settings for JWT
In your text editor, right click on the app directory, and paste this in (or create this manually if need be)
api/auth/[...nextauth]/route.ts
This will create the route.ts file at the correct location
NOTE: Make sure the api folder is the app directory!!
Add this dummy code for now:
// src/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth"
const handler = NextAuth()
export { handler as GET, handler as POST }
In Next.js, you can define an API route that will catch all requests that begin with a certain path. Conveniently, this is called Catch all API routes.
When you define a /pages/api/auth/[...nextauth] JS/TS file, you instruct NextAuth.js that every API request beginning with /api/auth/* should be handled by the code written in the [...nextauth] file.
The NextAuth function needs an options argument, which is an object of type AuthOptions
You can do this in the same file, but we’ll separate it out so we can export it
This will benefit us later as we’ll need it in other files as well
The options object needs at least providers, which is an array of Provider objects
In the [...nextauth] directory, create another file called options.ts
Add this code to it:
// src/app/api/auth/[...nextauth]/options.ts
import type { AuthOptions } from "next-auth";
export const options: AuthOptions = {
providers: []
};
NEXTAUTH_SECRET
Let’s pause here, and in a terminal window run this command:
openssl rand -base64 32
This value will be used to encrypt the NextAuth.js JWT, and to hash email verification tokens. This is the default value for the secret option in NextAuth and Middleware.
At the root level, create an .env.local file, and add the following:
Now that we have the Client ID and Secret, we can finish configuring the NextAuth options object
We’ll need the GitHubProvider from next-auth, so let’s import it:
// src/app/api/auth/[...nextauth]/options.ts
import GitHubProvider from 'next-auth/providers/github'
Now let’s update the providers array:
// src/app/api/auth/[...nextauth]/options.ts
import GitHubProvider from "next-auth/providers/github";
import type { AuthOptions } from "next-auth";
export const options: AuthOptions = {
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!
})
]
};
TypeScript will complain that when using the environment variable, type string | null is not assignable to type string. So, we use the ! to tell TypeScript it is definitely there. You could also use as string as well.
And that’s it for the GitHub setup!
Creating Pages
Now let’s set up the pages we’ll need
In the app directory, create a sign-in, sign-out, and profile folder
And then in each of those folders, create a page.tsx file. The code can look like this for now:
The sign-in and sign-out pages should be self explanatory. The profile page will be used to display basic info about the user from their GitHub profile page. This page should only be accessible once logged in.
While we’re at it, let’s create a simple Navbar component:
Since we are no longer using the default settings provided by NextAuth, we will not get the default “Login with GitHub” button which is styled.
So, we will need to create this component ourselves. Furthermore, this component will need to be a client component since we’ll be using the onClick event handler.
We can keep the sign-in page as a server component, and then add in the button as a child component.
This is my preferred approach: the page (parent) is a server component, and then any child components can be client components if need be.
In the components folder, create a SignInButton.tsx and SignOutButton.tsx:
For the signIn function, we need to pass in at least an id. Since we are using GitHub as our OAuth provider, we’ll use github. If you ran the GET request earlier, you would’ve seen what ids are available to you.
We’ll also specify the callbackUrl to tell NextAuth where to redirect the user once they are logged in. By default, you’ll be redirected to the same page that you logged in from. This is typically not the behaviour you want, so that is why we specify where to go.
Inside the component, we await the getServerSession, and pass in our options
We assign the result of this to the session variable
Then we conditionally render the JSX based on whether or not a session is active
You might be wondering why we specify a callbackUrl in the signIn function, as well as use the redirect function here in the page component.
The reason for this is that without this conditional rendering, we could still access the sign-in page while logged in by changing the URL manually. And as you’ll see shortly, the same applies to the sign-out page. We could still access the sign-out page, even though we are already logged out. So this is a nice extra layer of protecting our routes.
And that’s it! Thank you so much for reading this article, and I hope you found this simple demo helpful. This is my first time using NextAuth, so if you have any suggestions for improvement, please let me know. The repo can be found here, which you can use as a reference in case you get stuck.