Add NextAuth to NextJS 13 and Authenticate with GitHub

Tyler
Bits and Pieces
Published in
8 min readJan 13, 2023

--

NextJS 13 has come out, and with it, some minor changes to some typical things need to happen. In this article, we will add NextAuth and authenticate with Github.

The first thing we will do is download the necessary packages:

yarn add next-auth

That’s all we need for packages. It comes with its own Types so we are okay without a specific @types package.

NextJS 13 has moved from the old _app.js and is now hosting those in a RootLayout located at /app/layout.tsx (if your structure is similar to mine). In this RootLayout, we need to get access to the Session. We will then use the Session and pass that to our ContextProvider (aptly named AuthContext). The AuthContext will return our SessionProvider which comes from next-auth.

/app/layout.tsx

import { Session } from 'next-auth'
import { headers } from 'next/headers'
import AuthContext from './AuthContext';

async function getSession(cookie: string): Promise<Session> {
const response = await fetch(`${process.env.LOCAL_AUTH_URL}/api/auth/session`, {
headers: {
cookie,
},
});

const session = await response.json();

return Object.keys(session).length > 0 ? session : null;
}

export default async function RootLayout({
children,
}: {
children: React.ReactNode,
}) {
const session = await getSession(headers().get('cookie') ?? '');
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<AuthContext session={session}>
{children}
</AuthContext>
</body>
</html>
)
}

This is a pretty simple RootLayout. You can obviously change yours to be what you need.

Let’s break the file down:

async function getSession(cookie: string): Promise<Session> {
const response = await fetch(`${process.env.LOCAL_AUTH_URL}/api/auth/session`, {
headers: {
cookie,
},
});

const session = await response.json();

return Object.keys(session).length > 0 ? session : null;
}

We need to get our active session. To do this, next-auth has a nice function that lives in /api/auth/session. We will need to create the path to this stub. We will do that shortly. We get the session passing the cookies, and then return the session, or null if there is no session. I use a LOCAL_AUTH_URL env variable here to store my http://localhost:3000, so that I can update that on Vercel at deployment.

export default async function RootLayout({
children,
}: {
children: React.ReactNode,
}) {
const session = await getSession(headers().get('cookie') ?? '');
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<AuthContext session={session}>
{children}
</AuthContext>
</body>
</html>
)
}

In our main function here, we grab our session from our function. We use the headers() function provided by the next/headers package included in NextJS. Finally, we return our document and wrap our {children} in our AuthContext component.

/pages/api/auth/[…nextauth].ts

As mentioned above, next-auth provides an endpoint that handles basically everything. This is the file that you can slot in different providers (in our case, Github), and provide other important configurations.

import NextAuth, { AuthOptions } from "next-auth"
import GithubProvider from "next-auth/providers/github"
export const authOptions: AuthOptions = {
// Configure one or more authentication providers
providers: [
GithubProvider({
clientId: String(process.env.GITHUB_ID),
clientSecret: String(process.env.GITHUB_SECRET),
}),
],
callbacks: {
async signIn({ user }) {
let isAllowedToSignIn = true
const allowedUser = [
'YOURGITHUBACCID',
];
console.log(user);
if (allowedUser.includes(String(user.id))) {
isAllowedToSignIn = true
}
else {
isAllowedToSignIn = false
}
return isAllowedToSignIn
}
}
}

export default NextAuth(authOptions)

This file tells next-auth that we are going to use the GitHub provider. We require passing a clientId, and a clientSecret — obtainable via your github profile. I added a callback to double check again the signed-in user’s ID. I am sure there is a better method for this, but as an MVP this works fine. I will end up re-building the application under the organization, and checking to see if the user.organization matches.

Creating your GitHub application

The first thing you’ll want to do for this is to navigate to your developer settings.

Developer settings in sub-menu under account settings.

From there, you will pick ‘New GitHub App’, and fill in the required fields.

I turned webhooks off for now, as I don’t need any just yet. The Callback URL should be {url-where-next-auth-is}/api/auth/callback/{provider}. In our case, provider is Git Hub. If you used Google, you can put one here. You can also add more than 1 callback URL, for example, if you needed a localhost and a production instance.

After clicking create on the bottom, you will have to make note of the client ID. You will also need to generate a secret by clicking the ‘Generate a new client secret’ button. Store these values in your .env file under GITHUB_SECRET= and GITHUB_ID=

Post-clicking the button. Note, this app has been deleted, so I don’t care much about the values.

That’s all we need from Git Hub.

/app/AuthContext.tsx

"use client";

import { SessionProvider } from "next-auth/react";
import { Session } from "next-auth";

export interface AuthContextProps {
children: React.ReactNode;
session: Session
}

export default function AuthContext({ children }: AuthContextProps) {
return <SessionProvider>{children}</SessionProvider>;
}

This is a pretty straightforward component. The component wraps the children in a SessionProvider, essentially making the functionality from next-auth available everywhere.

Generate a NEXTAUTH_SECRET

You’ll want to generate a secret and store it in your .env file. You will use a similar value in a Vercel environment variable upon deployment. You can use the following command to generate a secret:

$ openssl rand -base64 32

Next Steps

We’re basically done with most of it by this point. The remaining pieces mostly come down to the logic of your application. In my instance, I wanted to provide a Navbar to those that were logged in and allow them to sign out. I also wanted to enforce authentication on every page.

/middleware.ts

export { default } from "next-auth/middleware"

NextJS will follow middleware put in the root directory named middleware.ts. In this file, I have a simple line, that tells the entire application to follow the next-auth/middleware. This will redirect everyone to the login page if they are not authenticated.

Authenticated NavBar

My application has a navigation that is persistent and appears on the left side of the screen. I won’t go into the details of the component itself. I am using MaterialUI, and a combination of components from that library to form it.

My application also serves everything under a /dashboard sub-URL. For example, my settings page is located at /dashboard/settings (in NextJS this pathing is /app/dashboard/settings/page.tsx). In NextJS 13, you can have multiple layouts that will be inherited from sub-paths. I created a layout.tsx in my /app/dashboard directory.

/app/dashboard/layout.tsx

'use client'
import NavBar from '../components/NavBar';

export default function dashboard({
children,
}: {
children: React.ReactNode,
}) {
return (
<>
<NavBar />
{children}
</>
)
}

This will make my NavBar show on every page under the /app/dashboard structure.

Inside my NavBar component, I want to provide a link to Sign out. Thankfully, you can easily do this by providing an onClick() handler to the signOut() function, provided by next-auth/react. Here’s a stub of the client component:

<Link key='signOut' onClick={() => signOut()}>
<ListItem key='signOut' disablePadding sx={{ display: 'block' }}>
<ListItemButton
sx={{
minHeight: 48,
justifyContent: 'initial',
px: 2.5,
}}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: { xs: 0, sm: 3 },
justifyContent: 'center',
}}
>
<Logout />
</ListItemIcon>
<ListItemText primary='Sign out' sx={{ display: { xs: 'none', sm: 'block' }, opacity: 1 }} />
</ListItemButton>
</ListItem>
</Link>
</List>

And at the top of that file:

'use client';
import { signOut } from 'next-auth/react';

Deploying to Vercel

Deployment is pretty straightforward to Vercel. I would recommend you follow the official documentation on their website. We just need to provide environment variables. We can safely do this, as all of our access of these variables is happening on the server components, and thus not accessed on the client.

That’s all there is to it. It took me a bit of time working around some TypeScript things, and ensuring that the edge functions worked out properly, but in the end everything works. Here are some screenshots:

My landing page / home page. Could just be the login page.
Post login, my NavBar appears. My Sign out button will log me out, and redirect me back to the login page.

Let me know if you have any questions or thoughts! It’s all new stuff, and NextJS 13 is in beta, so I wouldn’t be surprised if things change in the near future.

How to build Next apps with reusable components

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo

--

--

@tigatok | Software development focusing on newer technologies like NextJS 13, React 18, and other MACH Architecture,