Next.js Authentication with Okta and NextAuth.js 4.0

Featured image with Next.js, NextAuth.js, and Okta logos

Looking to add authentication to your Next.js application? Considering Okta as your login provider? You have come to the right place! This article covers adding authentication to your Next.js application with NextAuth.js and using Okta as a login provider. All with TypeScript in mind along the way.

If you don’t want to use Okta that’s fine, skip to section 3 to see how to use NextAuth.js with any properly configured provider.

Here’s the GitHub repository where I have all the code mentioned in this article. Also, I think it’s important to note the package versions that this article has been tested against: next 12.3.0, next-auth 4.13.0, react 18.2.0

1. Create A Brand New Next.js App and Install NextAuth.js

npx create-next-app --ts

This creates a brand new Next.js application for us and the --ts means the starter app will be initialized with TypeScript. Go through the options, name your app, and you’ll have a brand new Next.js app with TypeScript!

Change directories into your new app and the first thing you’ll do is install NextAuth.js. The package is called next-auth.

npm i next-auth@4.13.0

I’ve put the explicit @4.13.0 because that’s the latest version that I have tested for this article. I assume any 4.0 version will work but I wanted you to be aware of what I have explicitly tested against.

What is NextAuth.js? NextAuth.js is an open-source authentication solution created by the community that loves Next.js. It’s built with Next.js in mind the whole way for static, server side, and API route authentication. Check out the NextAuth.js introduction to learn more.

2. Create an OIDC Application in Okta

You need an OIDC application in Okta which you will authenticate against. I did this process using the Okta CLI but it can also be done by logging into Okta.com and creating an app there.

  1. Install the Okta CLI
    1. Easily done with Chocolatey choco install okta
  2. If you DO NOT have an account run okta register. If you DO have an account okta login
  3. Create your app with okta apps create
    1. Take the default app name or rename if you would like
    2. Choose the Web option for the Type of Application
    3. Choose Other for the Framework of Application
    4. Redirect URI: http://localhost:3000/api/auth/callback/okta
    5. Logout Redirect URI: http://localhost:3000
  4. You just created your Okta app in the CLI! You will see that a .okta.env file was created in your Next.js application. This holds the important environment files that you’ll need.

❗ Add .okta.env to your .gitignore file. The .okta.env file contains sensitive information that should not be in source control.

1// .okta.env created by CLI 2export OKTA_OAUTH2_ISSUER="URL of your Okta Account" 3export OKTA_OAUTH2_CLIENT_ID="Client ID - Preferred not to be shared" 4export OKTA_OAUTH2_CLIENT_SECRET="Client Secret - Definitely do not share"

3. Initial NextAuth.js setup & configuration

Now it’s time to get into using the next-auth package you installed and set up Okta as an authentication provider. With NextAuth.js it’s a similar workflow for adding additional providers, but I’ll be sticking with Okta for this example.

  1. Create a .env.local file at the root of your project. This is what the Next.js application will access to get any environment variables. Copy everything over from the .okta.env into your new .env.local but remove the export from in front of the variables.

❗ Make sure your .env.local does not go into source control now that you have put sensative information into it. If you created your app with npx create next app --ts it should already be in your .gitignore.

1// .env.local 2OKTA_OAUTH2_CLIENT_ID=asdfasdf 3OKTA_OAUTH2_CLIENT_SECRET=asdfasdfasdfasdf 4OKTA_OAUTH2_ISSUER=https://dev-27410971.okta.com/oauth2/default
  1. Two more variables need to be added to .env.local: NEXTAUTH_URL and SECRET.
    1. NEXTAUTH_URL will be http://localhost:3000 for development
    2. SECRET is generated by you. I used https://acte.ltd/utils/randomkeygen and copied the Encryption Key 256 to use for my value.
1// .env.local 2OKTA_OAUTH2_CLIENT_ID=asdfasdf 3OKTA_OAUTH2_CLIENT_SECRET=asdfasdfasdfasdf 4OKTA_OAUTH2_ISSUER=https://dev-27410971.okta.com/oauth2/default 5NEXTAUTH_URL=http://localhost:3000 6SECRET=123456asdf
  1. Now create a [...nextauth].ts file under the pages/api folder of your application. This is a special file used by NextAuth.js, that handles the sign-in and sign-out process for your Next.js application. Paste the following code into your file.
1//pages/api/[...nextauth].ts 2import NextAuth from 'next-auth' 3import Okta from 'next-auth/providers/okta' 4 5export const authOptions = { 6 // Configure one or more authentication providers 7 providers: [ 8 Okta({ 9 clientId: process.env.OKTA_OAUTH2_CLIENT_ID as string, 10 clientSecret: process.env.OKTA_OAUTH2_CLIENT_SECRET as string, 11 issuer: process.env.OKTA_OAUTH2_ISSUER as string, 12 }), 13 ], 14 secret: process.env.SECRET as string 15} 16 17export default NextAuth(authOptions)
  1. Now we move to the pages/_app.tsx file. We need to add a Provider that wraps our entire application here so that the NextAuth.js session data can be shared between components and pages.
1import '../styles/globals.css' 2import type { AppProps } from 'next/app' 3import { SessionProvider } from "next-auth/react" 4import { Session } from 'next-auth' 5 6export default function App({ 7 Component, 8 pageProps: { session, ...pageProps }, 9}: AppProps<{ session: Session; }>) { 10 return ( 11 <> 12 <SessionProvider session={session}> 13 <Component {...pageProps} /> 14 </SessionProvider> 15 </> 16 ); 17}

At this point, you have authentication setup. What is covered next is the way to use the next-auth package you installed and set up. This will work if you have set up Okta as I showed above or if you have properly set up one or more of the many other providers you can use with NextAuth.js.

4. Adding Authentication to Pages

Now it’s time to look at actually putting content behind authentication in your Next.js pages and API endpoints. Keep in mind this article makes no attempt at having a pretty user interface (UI). The focus is on authentication and I did not want to have any added complexity.

In this section, you will see three authentication implementation patterns:

  • Client Side Authentication
  • Server Side Authentication
  • Next.js API Route Authentication

Client Side Authentication

Authentication on the client side is not usually the best approach. The reason for this is that the data is sent to the client then authentication happens after. The visitor will not technically see any data on the page that they are not allowed to see but has the ability to access it if they really wanted to. Maybe you don’t have super sensitive that allows for client-side authentication.

Below is a code sample for executing client-side authentication for a Next.js page. It has a loading state that displays while the authentication status is being checked and then displayed the proper text and signIn or signOut button respectively.

1// client-auth.tsx 2import { signIn, signOut, useSession } from "next-auth/react" 3 4export default function ClientSideAuth() { 5 const { data: session, status } = useSession() 6 7 if(status === 'loading'){ 8 return ( 9 <> 10 Loading... 11 </> 12 ) 13 } 14 15 if (session) { 16 return ( 17 <> 18 You have logged in <button onClick={() => signOut()}>Sign out</button> 19 </> 20 ) 21 } 22 return ( 23 <> 24 Not Logged In <button onClick={() => signIn()}>Sign in</button> 25 </> 26 ) 27}

Notice the awesome signIn and signOut methods passed into the onClick handlers. These are provided by the next-auth/react package. These buttons will send users to the api/auth/[...nextauth].ts route you created and automatically handle the sign in and sign out process for you. It’s that easy to get login work in your application.

💡Only using one login provider? Consider modifying the code to use signIn('okta') and it will bring users directly to Okta’s login portal. This works for other providers as well, example signIn('github')

Server Side Authentication

Performing authentication server side is preferred. This is because the route will be pre-rendered by the server and only the appropriate content will make it to the client. If authenticated only that portion of the content is sent to the client and the same goes for unauthenticated content.

The below code is a Next.js page with getServerSideProps indicating the page is using server side rendering (SSR). It may look scary to use unstable_getServerSession call but that is the recommended method as of now. It is more performant than previous implementations provided NextAuth.js.

Server Side Redirect If Not Authorized

1// server-auth.tsx 2import { unstable_getServerSession } from "next-auth/next" 3import { authOptions } from "./api/auth/[...nextauth]" 4 5export async function getServerSideProps(context: any) { 6 const session = await unstable_getServerSession( 7 context.req, context.res, authOptions) 8 9 if (session) { 10 return { 11 props: { username: session?.user?.name } 12 } 13 } 14 return { 15 redirect: { destination: "/", permanent: false } 16 } 17} 18 19export default function ServerSideAuth(username: string) { 20 return ( 21 <> 22 <h1>Protected Page</h1> 23 <p>You can view this page because you are signed in as {username}</p> 24 </> 25 ) 26}

The above code checks the session within getServerSideProps to see if the user is authenticated. If they are we return the user's name and if they are not they get redirected to the homepage. For this app I do not have a dedicated login page, but if your application has a login page that would be a great place to send people that are not authorized.

💡 What does the permanent value mean in the redirect? Permanent can be one of two values true or false. If true the redirect will use the 308 status code which instructs clients/search engines to cache the redirect forever, if false will use the 307 status code which is temporary and is not cached.

Next.js API Route Authentication

If you need to add authentication to a Next.js API route you can use the same unstable_getServerSession that was used above in server side authentication.

1// api/api-auth.ts 2import { NextApiRequest, NextApiResponse } from "next"; 3import { unstable_getServerSession } from "next-auth/next"; 4import { authOptions } from "./auth/[...nextauth]"; 5 6export default async function handler( 7 req: NextApiRequest, 8 res: NextApiResponse 9) { 10 const session = await unstable_getServerSession(req, res, authOptions) 11 if (session) { 12 res.send({ 13 content: 14 "This is protected content. You can access this content because you are signed in.", 15 }) 16 } else { 17 res.send({ 18 error: "You must be signed in to view the protected content on this page.", 19 }) 20 } 21}

If authorized you send them the protected content, otherwise the code sends back and error to the user making the request.

Summary

With NextAuth.js authentication in Next.js become much simpler. With a little setup, you can easily add Okta support to your application, and after that initial setup, it’s very easy to add additional providers like GitHub, Google, or even bring in your own database.

You now have all the knowledge you need now to create a Next.js application that has content protected by an authentication layer connected to Okta. Remember server side authentication is the safer method for ensuring visitors only see the content they are supposed to.

Next-auth is a powerful library and I encourage you to explore its more advanced capabilities of it so you can be as informed as possible when it comes to authentication and the security of your application.

Here is the GitHub Repository with all the code from this article.

🚨Need help? Reach out to me on my Bio page or send me a message on Twitter. I’d love to see what you are building and help where I can.