# [How To] Authentication with IdentityServer4 and NextAuth v4 (Next.js App Router)

# Introduction

I recently worked on a project where I was responsible for the entire frontend, and authentication was handled through IdentityServer4. Initially, it was quite challenging because I had never integrated IdentityServer4 with the Next.js App Router before. In the past, I had used Next-Auth for a few projects with OAuth integrations (such as GitHub, Google, and credentials), and it worked well. Fortunately, Next-Auth also provides support for IdentityServer4 through a dedicated provider.

During the process of setting up authentication, I encountered several problems and issues. Finding up-to-date resources was difficult, and most blog posts only covered parts of the solution, with some information being inaccurate.

If you’re in a similar situation and want to learn how to seamlessly integrate authentication between the Next.js App Router and IdentityServer4, this guide is for you.

# Prerequisites

## Next.js

We will need a project with the version of Next 13.2 or above.

This is because Vercel introduced the App Router in Next 13. I was using Next 14 throughout the project, but it doesn't really matter. If you are new to Next 14 (latest stable version) I recommend checking one of my [previous blog post](https://blog.staridev.hu/how-to-authentication-with-identityserver4-and-nextauth-v4-nextjs-app-router), where I go through the features that Next 14 introduced.

## IdentityServer4

IdentityServer is an authentication server that implements OpenID Connect (OIDC) and OAuth 2.0 standards for [ASP.NET](http://ASP.NET) Core.

Our company had developed our own IdentityServer in the past couple of years, and many projects of ours still uses it. So for us, it was an easy choice as our backends are mainly written in .NET.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">IdentityServer4 has been deprecated and is superceded by Duende IdentityServer6.</div>
</div>

# Setup the environment variables

Just grab any existing Next project or create a new with:

```bash
npx create-next-app@latest
```

To make things work, we have to define our environment variables, without exposing them. Exactly for this purposes we need to create a `.env.local` file (if you want, you can use a .env file, note that in Next the `.env.local` will override the contents of the .env file). Both files should be in the root of your project at the same level as `app/`

These variables will hold the values of information that is needed to connect to the IdentityServer.

```javascript
.env.local

NEXTAUTH_URL = "https://localhost:3000/api/auth"
IdentityServer4_CLIENT_ID = "your_client_id"
IdentityServer4_CLIENT_SECRET = "your_client_secret"
IdentityServer4_ISSUER = "your_identity_server_issuer"
IdentityServer4_SCOPE = "your_identity_server_scopes"
NEXTAUTH_SECRET ="your_unique_secret"
```

Let's talk through all of these.

First you have to define the `NEXT_AUTH_URL.` This will point next-auth where it should listen for every request (signin, signout etc.). The mentioned value is default and probably will work just fine for you.

<div data-node-type="callout">
<div data-node-type="callout-emoji">📢</div>
<div data-node-type="callout-text">As you can see I wrote <code>https://</code> instead of <code>http://</code> although it is used in Next as default to expose your app. We will get back to this later</div>
</div>

The following four variables should reflect your settings in your IdentityServer4. These will tell next-auth where to go, and will help you authenticate to the IdentityServer. These variables can be set at the dashboard or directly in the database of your IdentityServer. This step depends on the service's provider, so it may vary. In our case we had to run a few scripts, to populate the database with the correct data. This step is crucial, if there is a mismatch between the server and client config, the connection will not build out.

When setting up your IdentityServer, make sure that the callback/redirect URL is `https://example.com/api/auth/callback/identity-server4`

The last thing we have to define is the `NEXT_AUTH_SECRET.` This random value will help next-auth to encrypt tokens during the authentication process. You can use any online tool to generate this, or the following command line command:

```plaintext
openssl rand -base64 32
```

# Changes to the package.json

IdentityServer4 requires every request to use SSL, so instead of the default `http://` we need to expose our app as `https://`.

It can happen, that you can communicate with your IdentityServer using simple http, but it is not recommended for production environments. Without https you may run into the following error:

```plaintext
localhost sent an invalid response.
ERR_SSL_PROTOCOL_ERROR
```

To use https in our Next project, we have to modify one line at the scripts section in the `package.json` file:

```plaintext
package.json

"scripts": {
    - "dev": "next dev",
	+ "dev": "next dev --experimental-https",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
```

With this flag, we can run our app at `https://localhost:3000`. As you can see, this is still experimental and Next.js will warn you after every `npm run dev` with the following:

```plaintext
Warning: Setting the NODE_TLS_REJECT_UNAUTHORIZED environment variable to '0' makes TLS connections and HTTPS requests insecure by disabling certificate verification.
```

You can read more about this here: [https://vercel.com/guides/access-nextjs-localhost-https-certificate-self-signed](https://vercel.com/guides/access-nextjs-localhost-https-certificate-self-signed)

This small change will solve the problem and raise a new one, but no worries, we will fix it in a minute.

# Self-signing certificates

We just setup our project to use `https`, instead of `http`, but by doing so we may face a new error:

```plaintext
https://next-auth.js.org/errors#signin_oauth_error self-signed certificate {
  error: {
    message: 'self-signed certificate',
    stack: 'Error: self-signed certificate\n' +
      '    at TLSSocket.onConnectSecure (node:_tls_wrap:1685:34)\n' +
      '    at TLSSocket.emit (node:events:519:28)\n' +
      '    at TLSSocket._finishInit (node:_tls_wrap:1085:8)\n' +
      '    at ssl.onhandshakedone (node:_tls_wrap:871:12)\n' +
      '    at TLSWrap.callbackTrampoline (node:internal/async_hooks:130:17)',
    name: 'Error'
  },
  providerId: 'identity-server4',
  message: 'self-signed certificate'
}
```

This is because our certificate is self-signed. After running the modified `npm run dev` script, you may notice that a new folder is created in the root of the project called `certificates.` This folder will contain the `localhost-key.pem` and `localhost.pem` files. These keys are automatically generated Next.js and will be used in local development.

To surpess this error in development, we need to add one more line to our freshly created `.env.local` file:

```plaintext
.env.local

NEXTAUTH_URL = https://localhost:3000/api/auth
IdentityServer4_CLIENT_ID = your_identity_server_client_id
IdentityServer4_CLIENT_SECRET = your_identity_server_client_secret
IdentityServer4_ISSUER = your_identity_server_issuer
IdentityServer4_SCOPE = "your_identity_server_scopes"
NEXTAUTH_SECRET ="your_unique_secret"

+ NODE_TLS_REJECT_UNAUTHORIZED = 0
```

In production if you are using Vercel or any other hosting service, they will handle the signing for you automatically. Self-signing certificates is not recommended in production, it leaves you with many security vulnerabilities.

# Let's setup next-auth

NextAuth is an open-source library for implementing authentication in Next.js projects. They provide easy to setup solutions for integrating the credentials provider (email + password) and OAuth. They have gone through a small "rebranding" and restructuring, so next-auth v5 is now called Auth.js. We will use next-auth, version 4 to be precise.

The NextAuth documentation can be found here: [https://next-auth.js.org/](https://next-auth.js.org/). While the new Auth.js docs are here: [https://authjs.dev/](https://authjs.dev/).

To setup next-auth, first we need to download the required dependencies. Just run the following command in your terminal:

```plaintext
npm install next-auth
```

The next step is to integrate the `SessionProvider` so we can use the things NextAuth provides throughout the app.

When using the app router in Next, there is a `app/layout.tsx` file that defines the `RootLayout` of the project. This is a server-component, therefore we can't use the provided `SessionProvider` here directly, because it uses `React.Context` under the hood.

Because of how Server Components work, we can nest Client Components in them, but not the other way around.

<div data-node-type="callout">
<div data-node-type="callout-emoji">📢</div>
<div data-node-type="callout-text">If this Server / Client component thing is all new to you, I highly recommend you to check out this video: <a target="_blank" rel="noopener noreferrer nofollow" href="https://youtu.be/VIwWgV3Lc6s?si=2T8E7R1aHM5aad9h" style="pointer-events: none">https://youtu.be/VIwWgV3Lc6s?si=2T8E7R1aHM5aad9h</a> by Theo - t3.gg. He will explain everything you need to know.</div>
</div>

So in order to use the `SessionProvider`, we have to create a custom provider, which is a Client component. You can create this file anywhere you want, the personal preference of mine is to put these type of things in the `lib` folder, in the root of the project.

```plaintext
lib/Providers.tsx

'use client';

import { ReactNode } from 'react';
import { SessionProvider } from 'next-auth/react';


export default function NextAuthProvider({
    children,
}: {
    children: ReactNode;
}) {
    return (
        <SessionProvider>
            {children}
        </SessionProvider>
    );
}
```

This won't do much, just wrap the NextAuth `SessionProvider` in a new client component.

Now we can use this in our `RootLayout`:

```plaintext
app/layout.tsx


import "./globals.css";
import type { Metadata } from "next";
+ import NextAuthProvider from "@/lib/Providers";

export const metadata: Metadata = {
  title: "How to setup IdentityServer4 with Next app router using next-auth",
  description: "If this blog post was helpful, please leave a like.",
};

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {

  return (
    <html lang="en">
      <body>
        + <NextAuthProvider>
            {children}
        + </NextAuthProvider>
      </body>
    </html>
  );
}
```

By doing this, we will access every feature of `next-auth` in every segment of our app. Obviously this file may vary and have more content, I just cleared it for simplicity.

---

Next we can define our auth configurations. To do that, create a file to store the config. I named it `authOptions.ts` and put it in the `lib/` folder. (You can name and put this file anywhere you want, it is up to you)

```plaintext
lib/authOptions.ts


import IdentityServer4Provider from 'next-auth/providers/identity-server4';
import { NextAuthOptions } from 'next-auth';

export const authOptions: NextAuthOptions = {

  secret: process.env.NEXTAUTH_SECRET,
  providers: [
    IdentityServer4Provider({
      id: 'identity-server4', // keep it as is, not placeholder value
      name: 'IdentityServer4', // keep it as is, not placeholder value
      issuer: process.env.IdentityServer4_ISSUER,
      clientId: process.env.IdentityServer4_CLIENT_ID,
      clientSecret: process.env.IdentityServer4_CLIENT_SECRET,
      authorization: { params: { scope: process.env.IdentityServer4_SCOPE } },
      wellKnown: `${process.env.IdentityServer4_ISSUER}/.well-known/openid-configuration`,
    }),
  ],

  callbacks: {
  // You can modify any callback of next-auth here, like jwt, session etc..
  },

  pages: {
    signIn: '/login',
  },
  session: { strategy: 'jwt' },
};
```

Basically we give the information that is needed to connect to the IdentityServer as parameters.

NextAuth should recognize and find the `wellKnown` (aka. discovery document), but for some reason it didn’t work for me, so provided it manually.

In the providers array, we can define many different providers such as Apple, Twitter and so on. We will only setup IdentityServer4 here. To learn more about the different providers, visit [https://next-auth.js.org/providers/](https://next-auth.js.org/providers/).

In the callback object you can override built-in callbacks such as `jwt`, `session` etc.. This is quite helpful if we want to destructure or modify the values stored in them. To learn more about callbacks visit [https://next-auth.js.org/configuration/callbacks](https://next-auth.js.org/configuration/callbacks).

Also we add the secret, the session strategy and the signin page's route. More on that later.

The last thing we have to do is to create a `route.ts` file in the `app/api/auth/[...nextauth]` folder. This is crucial, following this is mandatory.

```plaintext
app/api/auth/[...nextauth]/route.ts


import NextAuth from 'next-auth';  
import { authOptions } from '@/lib/authOptions';  

const handler = NextAuth(authOptions);  
export { handler as GET, handler as POST };
```

There is not much happening here, we expose the Route handler with the configuration we previously created. All requests to `/api/auth/*` (`signIn`, `callback`, `signOut`, etc.) will automatically be handled by NextAuth.js.

---

And now the setup of `next-auth` is done. But to try it out and create a "prod-like" experience we will do the following:

* We will create a login page. This is not rocket science, just let the user arrive at the gate of the app. There will be only one button here, that will fire the sign-in and if the IdentityServer4 is setup well, this will redirect the user there.
    
* We will create a sign-out button.
    
* We will create a middleware, to make the whole experience smooth and robust.
    
    * If the user is not authenticated, can not access any route in the app except `/login`.
        
    * If the user is authenticated by the IdentityServer, we will open the gates and the user can access the app.
        
    * While being signed in, the user can’t go back to the login page.
        
    * If there is a sign out, we will redirect the user to the login page.
        

Enough talking, get back to code.

# Creating a production like experience

To achieve the previously described functionality, first we have to code our login page. We can do this by creating a new folder in the `app/` directory called `login`. In this folder create the following `page.tsx`:

```plaintext
app/login/page.tsx


import { getServerSession } from 'next-auth/next'
import { redirect } from 'next/navigation'
import { authOptions } from '@/lib/authOptions'

export default async function LoginPage() {

    const session = await getServerSession(authOptions)

    if (session) {
        redirect('/')  
    }

    return (
        <div className="w-full h-full flex items-center justify-center">
           <LoginButton />
        </div>
    )
}
```

The first impression of you could be that this design is dog-water, and you are right. I just try to get rid of every single distraction and keep it simple.

In this file we get the session with the `getServerSession` function. But why Server session? Great question! You may noticed that this page is an `async` function. We can only define `async` functional components if we are in a Server Component. In Next app router, by default every component is a server component, so we don't have to write use `"use server";` at the top of the file. Until we want to add interactivity to a component, we can keep it as `"use server";` If we want to add interactivity (buttons for example) we need to switch to the `"use client";` directive manually, otherwise we will run into an error.

Back to the explanation. We need to pass the auth options to the `getServerSession` function and if the user is authenticated (it is a not null nor an empty session) we will redirect the user to the app.

This page will have only one button, but to get the session on the server and have the interactivity of a button, we have to create a new Client component, called `LoginButton.tsx`:

```plaintext
components/LoginButton.tsx


'use client' // we have interactivity (the user can click the button)

import { signIn } from 'next-auth/react'


export default function LoginButton() {

    return (
        <button onClick={() => signIn('identity-server4')}>
			Sign in with IdentityServer4
        </button>
    )
}
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">It is a good practice to keep as much as we can on the server side of our authentication flow, simply because of security purposes. This is why we outsource the LoginButton to a new file, although the component is small and doesn't do a lot.</div>
</div>

With this button placed in the Login page, we are almost there. Please style all the pages and components, it breaks my heart to not style all these things here.

Next up is to create our middleware to handle the redirection of users depending on there sessions existence. With a middleware we can protect our app from unauthenticated users. Create a `middleware.ts` file in the root of your project:

```plaintext
middleware.ts

import { withAuth } from 'next-auth/middleware'
import { NextResponse } from 'next/server'


export default withAuth(
 function middleware(req) {
 
    const isAuth = !!req.nextauth.token
    const isAuthPage = req.nextUrl.pathname.startsWith('/login')

    if (isAuthPage) {
      if (isAuth) {
        return NextResponse.redirect(new URL('/', req.url))
      }
      
      return null
    }

    if (!isAuth) {
      return NextResponse.redirect(new URL('/login', req.url))
    }
  },
  {
    callbacks: {
      authorized: () => true
    },
  }
)

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
```

This middleware uses the provided `next-auth` `withAuth` middleware. If the user in on the `/login` page and authenticated, we will redirect to `/`. If the user is not authenticated, we will redirect to the `/login` page. By doing this, unauthenticated users won't be able to access the app.

One thing that was a bit odd when I first used a middleware, was the matcher. The middleware will run on every single route by default, but we can exclude routes using the matcher attribute in the config object. We will be just fine with this matcher for now (and for most Next.js projects), you can read more about the `withAuth` middleware here: [https://next-auth.js.org/configuration/nextjs#middleware](https://next-auth.js.org/configuration/nextjs#middleware).

The last thing we need to do, is to create a sign out button. Good news is that, this will be the easiest part of the whole setup. All we need is to create a simple `handleSignOut` function and call it on any buttons `onClick` event. Something like this:

```plaintext
'use client';

const YourComponent = () => {

  const handleSignOut = async () => {
    await signOut({ redirect: false });
    router.push('/login');
  };

  return (
	  ...
	 <button onClick={handleSignOut}>Log out</button>
	 ...
  );

}
```

Aaand that's it. 🎉 We are done configuring authentication using Next.js (next-auth) and IdentityServer4.

# Call to action

Thank you for reading all the way down here! If you have any questions or found a bug, please feel free to reach out at any given platform.

If you found this post helpful, please consider a like and/or a follow.

Cheers! 👋👋
