How to Integrate Next-Auth with Your Next.js Application
Published: 2024-07-16 12:50:07

Recently, I migrated a frontend application from an older version of Next.js to Next.js 14. During this process, I decided to upgrade the session handling approach. After extensive research, I chose to use NextAuth.js, now known as Auth.js.

In the application, server-side authentication with AWS Cognito is utilized, and I wanted to maintain this setup. The goal was to seamlessly integrate authentication tokens and user data retrieved from successful authentications into Auth.js, ensuring session availability across the entire app and enabling authorization enforcement for different pages.

In this article, I'll guide you through the process of successfully integrating Auth.js with your Next.js application using the app router. We will cover both the credentials (email and password) approach and Google social login.

**Note:** Next-Auth v5 is still in beta mode, so I wouldn't fully recommend integrating it into a production application just yet. However, the final decision is up to you.

## Prerequisites

1. Familiarity with the basics of React.js
2. Brief experience with Next.js app router
3. Basic understanding of Typescript, as we'll be using Next.js with TypeScript

You don't need to have prior experience with Next-Auth.js (now Auth.js) to follow this article. 

**Note:** For the rest of the article,  I’ll be using `next-auth` and `Auth.js` interchangeably.

**Let's get started!!**

------------------------------------------------------


**1. Create your Next.js application**
Start by generating a new Next.js application:
```
npx create-next-app@latest
```
After running the command above, follow the prompts, accepting the defaults for TypeScript and Tailwind CSS as we'll be using TypeScript and will apply minor styling with Tailwind. Also choose `Yes` for the import alias and accept the default alias configuration.

**2. Install Auth.js**
Install the latest version of auth.js using npm or yarn
```
npm install next-auth@beta
```
or 

```
yarn add next-auth@beta
```
**3. Generate and Configure Your Environment Variables** 
Create a .env.local file and add your Auth.js secret. Auth.js uses this for encryption of your JWTs and cookies.
```
touch .env.local
```
Generate the secret string by running any of the two commands:

```
# Linux/Mac terminal
openssl rand -base64 33

# Alternatively, use `auth` to generate the secret
npx auth secret
```
Add the generated secret to your `.env.local` file:
```
AUTH_SECRET="your-secret"
```
You can escape running into major issues when you don't set this in your development environment. But it is compulsory to set it in the production environment.

You should also add your backend URL here 
```
NEXT_PUBLIC_BASE_URL="your-backend-url"
```

**4. Create your interface and types.**
To manage your types effectively, create a new directory named `types` in your root directory (or in your /src directory if you are using it), then inside it, add two new files: `login.ts` and `user.ts`.

```
├── app
├── public
├── types
│   ├── login.ts
│   ├── user.ts
├── .env.local
└── ...
```

Your app directory should look something like this with some other files and folders which are not shown here.

**`login.ts`**
Add the following types for different authentication scenarios:
```
type CredentialsType = {
  username: string;
  password: string;
};

type SocialCredentialsType = {
  auth_code: string;
};

export type { CredentialsType, SocialCredentialsType };

```
`CredentialsType`: Defines the structure for email and password login credentials.
`SocialCredentialsType`: Defines the structure for social login credentials, specifically for Google authentication.

**`user.ts`**
Define interface and type for user data to manage session details:
```
interface UserType {
  id: string;
  name: string;
  email: string;
  avatar: string;
  premiumSubscription: boolean;
  accessToken: string;
  refreshToken: string;
  subId: string;
}

type UserResponseType = {
  id: string;
  name: string;
  email: string;
  avatar: string;
  premium_subscription: boolean;
  access_token: string;
  refresh_token: string;
  sub_id: string;
};

export type { UserType, UserResponseType };

```

- `UserType`: Interface specifying the expected properties for user session management.
- `UserResponseType`: Type specifying the format of user data as received from the backend.

**5. Create your Auth.js configuration.**
Create a new file named `auth.ts` in your root directory. If you are using the `src/` directory approach, place the file directly within the `src/` folder.
```
├── app
├── public
├── types
│   ├── login.ts
│   ├── user.ts
├── .env.local
├── auth.ts
└── ...
```

**`auth.ts`**
At the top of the file import the necessary libraries and types for 
configuring Auth.js.
```
// library imports
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

// types imports
import type { NextAuthConfig, Session, User } from "next-auth";
import type { UserType, UserResponseType } from "@/types/user";
import { AdapterUser } from "next-auth/adapters";
import { CredentialsType, SocialCredentialsType } from "@/types/login";
import { JWT } from "next-auth/jwt";
```

Next, we have to modify some of the types in the next-auth library with some of our own properties.

```
declare module "next-auth" {
  interface User extends UserType {}
}

declare module "next-auth/adapters" {
  interface AdapterUser extends UserType {}
}

declare module "next-auth/jwt" {
  interface JWT extends UserType {}
}
```

Here, we used the TypeScript module augmentation feature to extend   the `User`, `AdapterUser`, `JWT` interfaces from NextAuth with properties defined in `UserType` from our `user.ts` file. This ensures that our custom user properties are recognized throughout the Auth.js configuration.

Next we declare `authOptions` object which we'll use to initialize `NextAuth` at the end.

```
const authOptions = {
  providers: [
// Add authentication providers here (e.g., CredentialsProvider, GoogleProvider)
  ],
  callbacks: {
 // Add custom authentication callbacks here (e.g., signIn, signOut, jwt, session)
  },
  pages: {
// Customize authentication-related pages (e.g., signIn, error)
  },
  session: {
// Configure session options (e.g., JWT settings, session management)
  },
} satisfies NextAuthConfig;

export const { handlers, auth, signIn, signOut } = NextAuth(authOptions);
```

**A.providers:** accepts an array of providers such as CredentialsProvider, GoogleProvider, e.t.c.

**B.callbacks:** specifies custom callback functions that can be triggered at various points in the authentication process.

**C.pages:** auth.js has default authentication-related pages for signIn, error, signOut e.t.c. But in this block , we can override it with our  customized pages by specifying their paths.

**D.session:** Configures session options, such as how sessions are handled and stored. e.g jwt or database approach

- Finally, we initialize `NextAuth` with the `authOptions` configuration and destructure the returned methods (handlers, auth, signIn, signOut) for use in our application.
    - `handlers`: Middleware for handling NextAuth requests.
    - `auth`: A universal method to interact with Auth.js in your Next.js app.
    - `signIn`: Method to trigger sign-in.
    - `signOut`: Method to trigger sign-out.

The code in the `auth.ts` file should look like this now
```
// library imports
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

// types imports
import type { NextAuthConfig, Session, User } from "next-auth";
import type { UserType, UserResponseType } from "@/types/user";
import { AdapterUser } from "next-auth/adapters";
import { CredentialsType, SocialCredentialsType } from "@/types/login";
import { JWT } from "next-auth/jwt";

declare module "next-auth" {
  interface User extends UserType {}
}

declare module "next-auth/adapters" {
  interface AdapterUser extends UserType {}
}

declare module "next-auth/jwt" {
  interface JWT extends UserType {}
}

const authOptions = {
  providers: [
  ],
  callbacks: {
  },
  pages: {
  },
  session: {
  },
} satisfies NextAuthConfig;

export const { handlers, auth, signIn, signOut } = NextAuth(authOptions);
```

Now, let's complete the `authOptions` object with the necessary values.

**A. providers**:
We will use `CredentialsProvider` for handling authentication on the server side by making requests to our server URL. We will set it up twice: once for email and password login, and once for social login with Google. To distinguish between the two, we specify unique id values.

```
providers: [
    CredentialsProvider({
      id: "credentials",
      name: "Credentials",
      authorize: async (credentials) => {
        try {
          const user = await fetchUser(
            `${process.env.NEXT_PUBLIC_BASE_URL}/auth/login`,
            {
              username:
                typeof credentials.username === "string"
                  ? credentials.username
                  : "",
              password:
                typeof credentials.password === "string"
                  ? credentials.password
                  : "",
            }
          );

          return user ? createUser(user) : null;
        } catch (error) {
          console.error("Error during authentication", error);
          return null;
        }
      },
    }),
    CredentialsProvider({
      id: "social",
      name: "Custom Social Login",
      authorize: async (credentials) => {
        try {
          const user = await fetchUser(
            `${process.env.NEXT_PUBLIC_BASE_URL}/auth/social_login`,
            {
              auth_code:
                typeof credentials.authCode === "string"
                  ? credentials.authCode
                  : "",
            }
          );

          return user ? createUser(user) : null;
        } catch (error) {
          console.error("Error during authentication", error);
          return null;
        }
      },
    }),
  ],
```

- **`Email and Password Login`**:
    - The first `CredentialsProvider` is configured for email and password login.
    - It makes a request to `${process.env.NEXT_PUBLIC_BASE_URL}/auth/login` with the provided credentials via the fetchUser function(replace this URL with your backend URL).
    - If a user is found, `createUser(user)` is called to format the user data. 


- **`Social Login with Google`**:
    - The second `CredentialsProvider` is configured for social login using Google.
    - It makes a request to `${process.env.NEXT_PUBLIC_BASE_URL}/auth/social_login` with the provided auth code via the `fetchUser` function(replace this URL with your backend URL).
    - If a user is found, `createUser(user)` is called to format the user data.

Each provider has a unique `id` to distinguish between the two methods of authentication.

By using `CredentialsProvider` twice with different `id` values, we can manage multiple authentication methods seamlessly.

Here are the definitions of the `fetchUser` and `createUser` functions:

```
// Function to authenticate and fetch user details
async function fetchUser(
  url: string,
  body: CredentialsType | SocialCredentialsType
) {
  try {
    const res = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });

    const user = await res.json();

    if (res.ok && user) {
      return user;
    } else {
      console.error(`Failed to fetch user: ${res.status} ${res.statusText}`);
      return null;
    }
  } catch (error) {
    console.error(`Error during fetch: ${error}`);
    return null;
  }
}

// Function to create a user object
function createUser(user: UserResponseType) {
  const userObject: UserType = {
    id: user.id,
    name: user.name,
    email: user.email,
    avatar: user.avatar,
    premiumSubscription: user.premium_subscription,
    accessToken: user.access_token,
    refreshToken: "", //add subId from the auth service here
    subId: "", // add refresh token here
  };

  return userObject;
}
```

**B. callbacks**:
The `callbacks` section allows us to customize the behavior of the authentication process. Here, we’ll add two async functions: `jwt` and `session`.
```
callbacks: {
    async jwt({ token, user }: { token: JWT; user: User }) {
      // Add the user properties to the token after signing in
      if (user) {
        token.id = user.id as string;
        token.avatar = user.avatar;
        token.name = user.name;
        token.email = user.email;
        token.premiumSubscription = user.premiumSubscription;
        token.accessToken = user.accessToken;
        token.subId = user.subId;
        token.refreshToken = user.refreshToken;
      }
      return token;
    },
    async session({ session, token }: { session: Session; token: JWT }) {
      // Create a user object with token properties
      const userObject: AdapterUser = {
        id: token.id,
        avatar: token.avatar,
        name: token.name,
        premiumSubscription: token.premiumSubscription,
        accessToken: token.accessToken,
        subId: token.subId,
        refreshToken: token.refreshToken,
        email: token.email ? token.email : "", // Ensure email is not undefined
        emailVerified: null, // Required property, set to null if not used
      };

      // Add the user object to the session
      session.user = userObject;

      return session;
    },
  },
```

- **`jwt callback`**:
    - The jwt function is called during the authentication process to add user properties to the JWT token. When a user signs in, their properties, such as id, avatar, name, email, accessToken, subId, and refreshToken, are added to the token. This token is then used to securely manage user sessions.

- **`session callback`**:
    - The session function is called whenever a session is accessed to add user data to the session object. A user object is created using the properties from the token. This user object is then added to the session, ensuring that user data is available whenever the session is accessed. The email property is ensured to be a string, and emailVerified is set to null if not used.

**C. pages**:
In the `pages` section of the `authOptions` object, we define paths to our custom pages that will override the default authentication-related pages provided by Auth.js. This allows us to customize the user experience for specific authentication actions.

```
  pages: {
    signIn: "/auth/login", // Custom sign-in page
    // error: "/auth/error", // Custom error page
  },
```

- **`signIn`**: Specifies the path to a custom sign-in page. By setting this to `/auth/login`, we direct users to our custom login page instead of the default Auth.js sign-in page.
- **`error`**: Optionally, we can specify a custom error page. This is commented out here, but if we wanted to use a custom error page, we could set its path like so: error: `/auth/error`.

**D. session**:
In the session section of the `authOptions` object, we specify how sessions should be managed. In this case, we are using JSON Web Tokens (JWT) as the session strategy.
```
  session: {
    strategy: "jwt",
  },
```

- **`strategy`**: By setting this to "jwt", we ensure that sessions are managed using JSON Web Tokens. This means that session data is encoded and stored in a JWT, which is then sent to the client.

Here is what the entire file should look like now
```
// library imports
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

// types imports
import type { NextAuthConfig, Session, User } from "next-auth";
import type { UserType, UserResponseType } from "@/types/user";
import { AdapterUser } from "next-auth/adapters";
import { CredentialsType, SocialCredentialsType } from "@/types/login";
import { JWT } from "next-auth/jwt";

// Modify NextAuth types with custom properties
declare module "next-auth" {
  interface User extends UserType {}
}

declare module "next-auth/adapters" {
  interface AdapterUser extends UserType {}
}

declare module "next-auth/jwt" {
  interface JWT extends UserType {}
}

const authOptions = {
  providers: [
    CredentialsProvider({
      id: "credentials",
      name: "Credentials",
      authorize: async (credentials) => {
        try {
          const user = await fetchUser(
            `${process.env.NEXT_PUBLIC_BASE_URL}/auth/login`,
            {
              username:
                typeof credentials.username === "string"
                  ? credentials.username
                  : "",
              password:
                typeof credentials.password === "string"
                  ? credentials.password
                  : "",
            }
          );

          return user ? createUser(user) : null;
        } catch (error) {
          console.error("Error during authentication", error);
          return null;
        }
      },
    }),
    CredentialsProvider({
      id: "social",
      name: "Custom Social Login",
      authorize: async (credentials) => {
        try {
          const user = await fetchUser(
            `${process.env.NEXT_PUBLIC_BASE_URL}/auth/social_login`,
            {
              auth_code:
                typeof credentials.authCode === "string"
                  ? credentials.authCode
                  : "",
            }
          );

          return user ? createUser(user) : null;
        } catch (error) {
          console.error("Error during authentication", error);
          return null;
        }
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }: { token: JWT; user: User }) {
      // Add the user properties to the token after signing in
      if (user) {
        token.id = user.id as string;
        token.avatar = user.avatar;
        token.name = user.name;
        token.email = user.email;
        token.premiumSubscription = user.premiumSubscription;
        token.accessToken = user.accessToken;
        token.subId = user.subId;
        token.refreshToken = user.refreshToken;
      }
      return token;
    },
    async session({ session, token }: { session: Session; token: JWT }) {
      // Create a user object with token properties
      const userObject: AdapterUser = {
        id: token.id,
        avatar: token.avatar,
        name: token.name,
        premiumSubscription: token.premiumSubscription,
        accessToken: token.accessToken,
        subId: token.subId,
        refreshToken: token.refreshToken,
        email: token.email ? token.email : "", // Ensure email is not undefined
        emailVerified: null, // Required property, set to null if not used
      };

      // Add the user object to the session
      session.user = userObject;

      return session;
    },
  },
  pages: {
    signIn: "/auth/login", // Custom sign-in page
    // error: "/auth/error", // Custom error page
  },
  session: {
    strategy: "jwt",
  },
} satisfies NextAuthConfig;

// Function to authenticate and fetch user details
async function fetchUser(
  url: string,
  body: CredentialsType | SocialCredentialsType
) {
  try {
    const res = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });

    const user = await res.json();

    if (res.ok && user) {
      return user;
    } else {
      console.error(`Failed to fetch user: ${res.status} ${res.statusText}`);
      return null;
    }
  } catch (error) {
    console.error(`Error during fetch: ${error}`);
    return null;
  }
}

// Function to create a user object
function createUser(user: UserResponseType) {
  const userObject: UserType = {
    id: user.id,
    name: user.name,
    email: user.email,
    avatar: user.avatar,
    premiumSubscription: user.premium_subscription,
    accessToken: user.access_token,
    refreshToken: "", //add subId from the auth service here
    subId: "", // add refresh token here
  };

  return userObject;
}

export const { handlers, auth, signIn, signOut } = NextAuth(authOptions);
```
**6. Utilize the configuration in the Next.js API route.**
To integrate the `NextAuth` configuration into a Next.js API route, follow these steps:

Create a new file named `route.ts` in the app directory at the following path:
```
├── app
│   ├── api
│   │   ├── auth
│   │   │   ├── [...nextauth]
│   │   │   │   ├──  route.ts
├── auth.ts
└── ...
```

In the `route.ts` file, add the following lines of code:
```
// library imports
import { handlers } from "@/auth";

export const { GET, POST } = handlers;
```

At the top, we import the `handlers` object from `auth.ts`, then destructure it to extract the `GET` and `POST` methods and exports them. When a request is made to the `/api/auth` endpoint, the `route.ts` file delegates the request to the appropriate handler (`GET` or `POST`) provided by NextAuth. These handlers manage the authentication logic, session management, and other related tasks based on the configuration we defined earlier in `auth.ts`.

We are done with setting up `Auth.js`. Now let's use the magic we created and see it in action

------------------------------------------------------

**7. Using Auth.js in our Next.js Application**
Now that we have completed the setup for `Auth.js`, it's time to see it in action. We'll integrate authentication into our application by creating email and password sign-in, social login and protected pages.

**A. Create the authentication components**
First, create a components directory in the root directory of your app (in the `src/` directory if you are using this approach). Then add `auth` and `protected` directories in it.
 
- Create a new file named `login.tsx` in the `components/auth` directory and a new file named `profile.tsx` in the `components/profile` directory:
```
├── app
├── components
│   ├── auth
│   │   ├── login.tsx
│   ├── profile
│   │   ├── profile.tsx
```

- **`login.tsx`**:
Here we will add the following code to create a sign-in form and a google login button.
```
"use client";

// library imports
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";

export default function SignIn() {
  const searchParams = useSearchParams();
  const googleLogin = "Your google login url which will contain your client id and redirectURI";

  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [authenticated, setAuthenticated] = useState(false);
  const [error, setError] = useState("");

  useEffect(() => {
    if (authenticated) {
      // Redirect to previous page or home page
      const next = searchParams.get("next") || "/";
      window.location.href = next;
    }
  }, [authenticated]);

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    try {
      const res = await fetch("/api/login", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ username, password, type: "credentials" }),
      });

      if (res.ok) {
        setAuthenticated(true);
      } else {
        // handle error state here
        setError("Invalid credentials");
      }
    } catch (error) {
      // handle error state here
      console.error("Error during sign-in", error);
      setError("Internal server error");
    }
  };

return (
    <div className="mx-auto w-[200px] h-full border-red-100">
      <div>
        <p className="text-xl w-full flex justify-center mt-3 mb-5">Sign In</p>
        <form onSubmit={handleSubmit}>
          <label>
            Username:
            <input
              type="text"
              className="w-full rounded-sm"
              value={username}
              onChange={(e) => setUsername(e.target.value)}
            />
          </label>
          <label>
            Password:
            <input
              className="w-full rounded-sm"
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
            />
          </label>
          <button
            className="w-full flex justify-center bg-teal-500 text-white mt-3 rounded-md"
            type="submit"
          >
            Sign In
          </button>
          {error && <p style={{ color: "red" }}>{error}</p>}
        </form>
      </div>
      <div className="my-2">
        <div className="flex justify-center"> or </div>
      </div>
      <div className="w-full bg-red-700 rounded-md mb-2">
        <Link href={googleLogin} className="flex ">
          <p className="w-full text-white flex justify-center">
            Sign in with Google
          </p>
        </Link>
      </div>
    </div>
  );
}

```

We'll send a POST request to an API route (/api/login) to handle authentication. If the login is successful, the user will be redirected to the next page or home page. If the `Sign in with Google` button is clicked, the user will be redirected to the `google-login` page

Next, we will create the `/api/login` endpoint to handle the authentication logic on the server side. Create a new folder named `login` in the `app/api` directory and create a `route.ts` file in it.
```
├── app
│   ├── api
│   │   ├── auth
│   │   ├── login
│   │   │   ├── route.ts
├── components
├── auth.ts
└── ...
```

- **`route.ts`**:
```
// library imports
import { NextResponse, NextRequest } from "next/server";

// internal imports
import { signIn } from "@/auth";

export async function POST(req: NextRequest, res: NextResponse) {
  const data = await req.json();
  const { username, password, type } = data;

  try {
    const result =
      type === "credentials"
        ? await signIn("credentials", { redirect: false, username, password })
        : await signIn("social", { redirect: false, authCode: username });

    // handle the result of the sign-in attempt
    if (!result || result.error) {
      return NextResponse.json({ error: "Invalid credentials" });
    } else {
      return NextResponse.json({ success: true });
    }
  } catch (error) {
    console.error("Error during sign-in", error);
    return NextResponse.error();
  }
}
```
Here, we handle authentication for both credentials and social login types. Based on the `type` of login, we call the signIn function with either credentials (`username and password`) or social login (`auth_code`).

Next, we will create the social login page. Create a new file named `socialAuth.tsx` in the `components/auth` directory.

```
├── app
├── components
│   ├── auth
│   │   ├── login.tsx
│   │   ├── socialAuth.tsx
│   ├── profile
```

-  **`socialAuth.tsx`**:
Add the following code to socialAuth.tsx.
```
"use client";

// library imports
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";

export default function SocialAuth() {
  const googleLogin =
    "Your google login url which will contain your client id and redirectURI";

  const router = useRouter();
  const searchParams = useSearchParams();

  const [authSuccess, setAuthSuccess] = useState(true);
  const [tokenStatus, setTokenStatus] = useState(false);

  useEffect(() => {
    // check query string for authentication code
    if (authSuccess || tokenStatus) {
      const url = window.location.href;
      const code = url.match(/\?code=(.*)/);
      if (!tokenStatus && authSuccess) {
        if (code) {
          authenticateUser(code[1]);
        } else {
          router.push("/auth/login");
        }
      } else if (tokenStatus) {
        // Redirect to previous page or home page
        const next = searchParams.get("next") || "/";
        router.push(next);
      } else {
        router.push("/auth/login");
      }
    }
  }, [tokenStatus, authSuccess]);

  const authenticateUser = async (code: string) => {
    try {
      const res = await fetch("/api/login", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ username: code, type: "social" }),
      });

      if (res.ok) {
        setTokenStatus(true);
      } else {
        // handle error state here
        setAuthSuccess(false);
      }
    } catch (error) {
      // handle error state here
      setAuthSuccess(false);
    }
  };

  return (
    <>
      <div>
        {authSuccess ? (
          <div>
            <h1>Authenticating...</h1>
          </div>
        ) : (
          <div>
            <h1>
              {" "}
              An error occurred while attempting to authenticate your account
              with Google{" "}
            </h1>

            <div>
              <div>
                <Link href={googleLogin}>Please try again</Link>
              </div>
            </div>
          </div>
        )}
      </div>
    </>
  );
}
```
This ensures that users can authenticate via social login providers like Google and handles the authentication flow smoothly.

**B. Create the Authentication Pages**
Next, let's create the custom `sign-in` and `social login` pages that utilize the components we created above.

Create a new directory called `auth` in the `app/` directory. Inside `auth`, create folders named `login` and `social`, and then add a `page.tsx` file in both folders.
```
├── app
│   ├── auth
│   │   ├── login
│   │   │   ├── page.tsx
│   │   ├── social
│   │   │   ├── page.tsx
```
- In `login/page.tsx`, add the following code:
```
//component imports
import SignIn from "@/components/auth/login";

export default function LoginPage() {
  return <SignIn />;
}

```

- In `social/page.tsx`, add the following code:
```
//component imports
import SocialAuth from "@/components/auth/socialAuth";

export default function SocialAuthPage() {
  return <SocialAuth />;
}
```

We are now set with authentication, and you can confirm the state of what we have done so far.

- Attempt to navigate to this url `http://localhost:3000/api/auth/signin`. You should be redirected back to the custom sign-in page. This redirection occurs because of the URL path we specified in the `pages` section of our `authOptions` object while setting up the configuration in `auth.ts`.

**B. Create protected page**
The final step is to create a protected page that users cannot access unless they are authenticated.

-**`profile.tsx`**
Let's add the code below to our empty file `profile.tsx` in the components directory.
```
"use client";

// library imports
import React, { useState, useEffect } from "react";
import { useSession } from "next-auth/react";
import { useRouter, usePathname } from "next/navigation";

export default function Profile() {
  const router = useRouter();
  const pathname = usePathname();

  const { data: session } = useSession();

  const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;

  const [loadingProfile, setLoadingProfile] = useState(false);

  useEffect(() => {
    if (session?.user?.accessToken) {
      // fetch user profile if access token is available
      getUserProfile(session.user.accessToken);
    } else {
      // Redirect to `/login` if no access token or no session
      router.push("/auth/login?next=" + pathname);
    }
  }, []);

  const getUserProfile = (token: string) => {
    setLoadingProfile(true);
    fetch(`${baseUrl}/auth/user_profile`, {
      headers: { Authorization: "Bearer " + token },
    })
      .then((response) => {
        setLoadingProfile(false);
      })
      .catch((error) => {
        // handle error here
        console.error(error);
      });
  };

  return (
    <>
      {" "}
      {loadingProfile ? (
        <p>Loading...</p>
      ) : (
        <div>
          <p>User Profile</p>
          <p>Name: {session?.user?.name}</p>
          <p>Email: {session?.user?.email}</p>
        </div>
      )}
    </>
  );
}

```

Here we use the `useSession` hook to check if a user session with an access token exists. If the token is present , we go ahead and make a call to the backend route `/auth/user_profile` . In my case i have a protected route in the backend which an unauthenticated user cannot access without a token. While waiting for the profile data to load, a loading indicator is displayed. Once loaded, the component displays the user's name and email. If there is no active session or access token, it redirects the user back to the login page to authenticate.

Finally, we create the page which utilizes the `profile.tsx` component above. Create a new folder in the app directory named `profile` and in it a `page.tsx` file. 

-  **`page.tsx`**:
```
// library imports
import { SessionProvider } from "next-auth/react";

// internal imports
import { auth } from "@/auth";

//component imports
import Profile from "@/components/profile/profile";

export default async function ProfilePage() {
  const session = await auth();

  return (
    <SessionProvider session={session}>
      <Profile />
    </SessionProvider>
  );
}
```

First we import the `SessionProvider` component from `next-auth/react` to serve as a context provider for managing the user session state throughout the application. Additionally, we import the `auth` method from `auth.js`. Since this operation requires asynchronous behavior, we utilize a server-side component, using `await` to fetch the session using the `auth` method. Subsequently, we pass this session object obtained from auth to the `SessionProvider` component which acts as a wrapper around the `Profile` component. We have to wrap the component with Session Provider here if we want the session to be accessible within `profile.tsx` using the `useSession` hook, similar to how we handled authentication state above.

Now if we try to access the `/profile` route, we would get redirected to the `/auth/login` page since we are not logged in. Once we login, we would be redirected back to the profile page, where we can see our user data.

------------------------------------------------------
Congratulations , you have successfully integrated your next js application using the app router with auth.js v5.
------------------------------------------------------

You might have noticed that in the profile.tsx component, we check the session for a token and then redirect the user back to the login page if the token is not present. This means that Next.js already starts rendering our page before redirecting to the login page if the token is absent. If you pay attention, you'll also notice this in the UI.

An alternative to this approach is to use middleware to handle authentication. Next.js has a middleware feature that is triggered when a user attempts to access a route. We can use this to protect our authenticated routes. This way, the protected page is not rendered if an unauthenticated user attempts to access it, as the middleware intercepts the request and redirects the user to the login route. This ensures we have a central location to check the session and redirect users if they are unauthenticated, thereby improving the user experience with the UI.

**8. Adding a Middleware**
Create a new file called `middleware.ts` in your root directory. If you are using the `src/` directory approach, place the file directly within the `src/` folder.
```
├── app
├── public
├── types
├── .env.local
├── auth.ts
├── middleware.ts
└── ...
```

-**`middleware.ts`**
In the file add the following lines of code:
```
import { NextResponse } from "next/server";
import { auth } from "@/auth";

export default auth((req) => {
  const currentPath = req.nextUrl.pathname;

  // Redirect to login page if user is not authenticated
  if (!req.auth) {
    return NextResponse.redirect(
      new URL(`/auth/login?next=${currentPath}`, req.url)
    );
  }
});

// Manage list of protected routes
export const config = {
  matcher: ["/profile/:path*", "/another-protected-route/:path*"],
};

```
This middleware checks if the user is authenticated before allowing access to specified routes. If the user is not authenticated, they are redirected to the login page. The config object defines the routes that should trigger this middleware, ensuring that only protected routes are affected.

By setting up this middleware, you centralize the authentication logic, making your codebase cleaner and more maintainable as your application grows.

**`Note`**: In my application, I have several authenticated and unauthenticated routes, which is why I am not triggering the middleware with all requests and instead maintaining a protected routes list. The little downside to this is that you have to manage a list of all your protected routes. This might end up becoming a long list and could become difficult to manage, or someone might implement a protected page and forget to update the middleware. If your base path is the only unprotected route, your job will be much easier.

It's been a long ride, but we have finally come to the end of this tutorial. With what you have learned, I believe you can seamlessly integrate `Auth.js v5` into your `next.js application.  

Here is the link to the [github repo](https://github.com/Faruqt/next-14-authjs) for this project. Cheers!!!

If you have any questions, feel free to drop them as a comment or send me a message on [LinkedIn](https://www.linkedin.com/in/faruq-abdulsalam/) and I'll ensure I respond as quickly as I can. Ciao 👋