Implementing User Authentication in Next.js 14 using AuthJs Credentials (REST API)
Published: 2024-02-19 04:55:13

Let’s talk step by step about how to implement Next.js 14’s user authentication with Auth.js (credentials) and REST API.

To start, begin by creating a [Next.js](https://nextjs.org/docs/app/api-reference/create-next-app) application.

`npx create-next-app@latest`

Then, integrate the [Auth.js](https://authjs.dev/reference/nextjs) library into your application.

`npm install next-auth@beta`
Next, you will need to add a `AUTH_SECRET` and `NEXTAUTH_URL` to your `env` file. Although during development you could leave `AUTH_SECRET` it empty, during production it would raise an error.

Now, proceed to create the `auth.ts` file within your `/src` directory. This file will handle authentication by interfacing with a REST API, specifically for login functionality. Depending on your API structure, the response may vary, but you can reference the example API response provided here to guide your implementation.

```
import type {NextAuthConfig} from "next-auth";
import NextAuth from "next-auth";

import CredentialsProvider from "next-auth/providers/credentials";
import $axios from "@/lib/Axios";

async function login(credentials: any) {
    try {
        return await $axios.post("your-login-api", credentials).then((res: any) => {
            const {user} = res;
            return {
                name: user.name,
                email: user.email,
                image: user.profile_photo,
                accessToken: res.access_token,
                // If you need any other information you can add here...
            };
        });
    } catch (e) {
        throw new Error("Something went wrong.");
    }
}

export const config: NextAuthConfig = {
    pages: {signIn: "/login"},
    providers: [
        CredentialsProvider({
            name: "Credentials",
            credentials: {
                email: {label: "Email", type: "email", placeholder: "example@email.com"},
                password: {label: "Password", type: "password", placeholder: "******"}
            },
            async authorize(credentials,) {
                try {
                    return login(credentials);
                } catch (e) {
                    return {};
                }
            },
        }),
    ],
    callbacks: {
        async jwt({user, token}) {
            if (user) {
                token.user = user;
            }
            return token;
        },
        async session({session, token}: any) {
            session.user = token.user;
            return session;
        },
    },
    debug: process.env.NODE_ENV === "development",
};

export const {handlers, auth, signIn, signOut} = NextAuth(config);
```
The `jwt()` callback is called user when the user first logs in. The user object will be populated with the object that is returned from the authorize function. The object that is returned from the `jwt` callback is what will be saved on the session cookie.

The `session()` callback receives the session cookie content in its token parameter. Whatever is returned from this callback is what will be presented when `useSession` or `getServerSession` is called.

Next, establish a `api` directory within either your `/src/app` or `/app` directory. Inside this api directory, create a `route.ts` file within the `api/auth/[...nextauth]` directory structure.

```
import {handlers} from "@/auth";

export const {GET, POST} = handlers;
Then create another route.ts file inside api/protected directory.

import {auth} from "@/auth";

export const GET = auth((req) => {
    if (req.auth) {
        return Response.json({data: "Protected data"});
    }

    return Response.json({message: "Not authenticated"}, {status: 401});
}) as any;
```
In your `layout.tsx` file, you'll utilize the `SessionProvider` to manage session data. This component enables your application to interact with the user authentication state across various components. You can use it to display different content or UI elements based on whether a user is authenticated or not.

```
"use client";
import React from "react";
import {SessionProvider} from "next-auth/react";
...

export default function RootLayout({children}: { children: React.ReactNode }) {
    return (
        <SessionProvider>
            <html lang="en">
            <body className="pintoe">
                {children}
            </body>
            </html>
        </SessionProvider>
    );
}
```
Well done, your [Auth.js](https://authjs.dev/reference/nextjs) setup is complete. Now you can use your session data on every page (route). ex: `app/pages.tsx`

```
"use client";

import {useSession} from "next-auth/react";
import Image from "next/image";

export default function HomePage() {
    const {data: session} = useSession();
    
    return (
        <div>
            <h2>{session?.user.name}</h2>

            <Image src={session?.user.image} alt="User Avatar" width={200} height={200}/>
        </div>
    );
}
```
Now you can use a higher order component to protect authenticated routes. On my side, I create a `AuthGuard.tsx` file in `src/` components directory.

```
"use client";
import React, {useEffect, useState} from "react";
import {useSession} from "next-auth/react";
import {usePathname, useRouter} from "next/navigation";

export default function AuthGuard({children}: { children: React.ReactNode }) {
    const {data: status}: any = useSession();
    const router = useRouter();
    const pathname = usePathname();
    const [loading, setLoading] = useState<boolean>(true);

    const guestRoutes: string[] = [
        "/login",
        "/register"
    ];

    useEffect(() => {
        const isGuestRoute = !!guestRoutes.find(route => route === pathname);
        setLoading(true);

        if (status === "loading") {
            return;
        } else if (status === "authenticated" && isGuestRoute) {
            router.push("/");
        } else if (status === "unauthenticated" && !isGuestRoute) {
            router.push("/login");
        } else {
            setLoading(false);
        }
    }, [pathname, status]);

    return (loading ?
        <div>
            Loading...
        </div> : children);
};
```
Use the `AuthGuard` component in your `layout.tsx` file.

```
"use client";
import "@/assets/styles/tailwind.css";
import React from "react";
import {SessionProvider} from "next-auth/react";
import AuthGuard from "@/lib/AuthGurad";

export default function RootLayout({children}: { children: React.ReactNode }) {
    return (
        <SessionProvider>
            <html lang="en">
            <body className="pintoe">
            <AuthGuard>
                {children}
            </AuthGuard>
            </body>
            </html>
        </SessionProvider>
    );
}
```
Now the AuthGuard component will automatically handle guest/auth route redirects based on login session data.

Next, we will discuss step by step how to implement `redux-toolkit` in [Next.js 14](https://nextjs.org/) and how to configure [Axios](https://axios-http.com/) to get `access-token` from the auth session.