NextAuth.js: The Ultimate Security Solution for Your Next.js App

NextAuth.js: The Ultimate Security Solution for Your Next.js App

Securely authenticating, in under 5 minutes !

ยท

6 min read

Introduction

In web development, authentication is crucial for users to verify their identities and access essential resources. NextAuth.js is a highly acclaimed authentication library specifically designed for Next.js applications that provide a versatile and user-friendly authentication handling approach. This article explores how utilizing NextAuth can boost the security of your Next.js 13 web app.

Benefits ๐Ÿ‘€

First, let's talk about how NextAuth.js is beneficial to our Next.js application.

Easy Set-Up

Next-auth is an exceptional platform known for its easy setup process. With just a few lines of code, you can configure authentication for your Next.js application within minutes. This versatile platform supports various authentication providers, including Google, Facebook, Twitter, GitHub, and more. Moreover, Next-auth offers email and password-based authentication, which is ideal for applications that do not want to depend on third-party providers. Additionally, the platform's intuitive API enables the creation of custom authentication providers with ease.

Secure Authentication

To ensure the secure authentication and authorization of your Next.js application, NextAuth.js provides a comprehensive range of advanced security features. These features include signed, prefixed, and server-only cookies, which are designed to prevent unauthorized tampering attempts. Additionally, NextAuth.js supports HTTP POST requests with CSRF token validation to prevent cross-site request forgery attacks. The platform also leverages JSON Web Tokens (JWT) with JSON Web Signature (JWS), JSON Web Encryption (JWE), and JSON Web Key (JWK) to provide secure authentication and authorization.

Flexibility

NextAuth.js is an ideal solution for serverless environments, with the ability to operate in any setting. It provides the freedom to use or not use a database and supports numerous options, such as MySQL, Postgres, MSSQL, MongoDB, and many others. You can manage user sessions with either database sessions or JSON Web Tokens (JWT). With secure web pages and API routes, NextAuth.js is an excellent choice for applications that require top-notch security.

Setting up NextAuth (takes < 5 mins) ๐Ÿ› 

While there is ample information on logging in through social media platforms like Google and GitHub, I believe it is important to highlight the process of logging in with NextAuth.js and Credentials, powered by the MongoDB database.

Installation

npm i next-auth

If you are using TypeScript, NextAuth.js comes with its types definitions within the package. To learn more about TypeScript for next-auth, check out the TypeScript documentation.

Now that we have installed the package, it's time to add an API route to handle all the APIs.

Adding API route

To add NextAuth.js to a project create a file called [...nextauth].js in pages/api/auth. In the example below I have,

  • Connected to my MongoDB database

  • Can log in via Google, GitHub, and Credentials (email, password)

  • Secret : A random string is used to hash tokens, sign/encrypt cookies, and generate cryptographic keys.

import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import GoogleProvider from "next-auth/providers/google";
import GithubProvider from "next-auth/providers/github";
import mongoose from "mongoose";
import Users from "../../../models/UserSchema";

const dbConnect = () => {
  if (mongoose.connection.readyState >= 1) return;
  mongoose.connect(process.env.MONGO_URI);
};

export const authOptions = {
  session: {
    strategy: "jwt",
  },
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    CredentialsProvider({
      async authorize(credentials, req) {
        dbConnect();

        const { email, password } = credentials;

        const user = await Users.findOne({ email });

        if (!user) {
          throw new Error("Invalid Email or Password");
        }

        if (user.password !== password) {
          throw new Error("Invalid Email or Password");
        }

        return user;
      },
    }),
  ],
  pages: {
    signIn: "/",
  },
  secret: process.env.NEXTAUTH_SECRET,
};

export default NextAuth(authOptions);

All requests to /api/auth/* (signIn, callback, signOut, etc.) will automatically be handled by NextAuth.js. Read more here

Shared Session state

Now that the API is ready, we need to define <SessionProvider /> at the very top-level. Since we are using NextJS 13 we can do it in the layout.jsx file in the app directory.

"use client"

import Navbar from '@/components/Navbar/Navbar'
import Link from 'next/link'
import Script from 'next/script'
import '../styles/globals.css'
import { SessionProvider } from "next-auth/react";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <title>ShopCoders</title>
        <meta content="width=device-width, initial-scale=1" name="viewport" />
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />


      </head>

      <body>

        <SessionProvider>
          <Navbar />
          {children}
        </SessionProvider>

      </body>
    </html>
  )
}

Credential login functionality

Okay, so we are one step closer to the log in part. Now we just need to add the `useSession()` react hook. It is also the easiest way to check if someone is signed in.

Consider that you have a Login page where you input the email and password and on clicking the Login the button you run a function to login.

const [creds, setcreds] = useState({ email:"", password:""});

const handleLogin = async () => {
        try {
            const email = creds.email;
            const password = creds.password;
            const data = await signIn("credentials", {
                redirect: false,
                email,
                password,
            });

            if (data.error) {
                showErrorToast(data.error);
            }

            if (data.ok) {
                showSuccessToast("Logged in");
            }
        } catch (error) {
            showErrorToast(error);
        }
    };

Securing any API

So now that we are all secured, we can secure any API such that those are accessible only if the user is Logged in. So let us consider an example.js file , so whenever we visit /api/example we need to be Logged in, else it will simply show errors.

import connectDb from "../../../middleware/db";
import { authOptions } from "../auth/[...nextauth]";
import { getServerSession } from "next-auth/next";

const handler = async (req, res) => {
  try {
    const session = await getServerSession(req, res, authOptions);
    console.log(session?.user);

    if (!session) {
      res.status(401).json({ message: "You must be logged in." });
      return;
    }

    return res.status(200).json(session.user);
  } catch (error) {
    console.log(error);
  }
};

export default connectDb(handler);

Securing Client side

We can secure the client-side too. Let's consider the secureclient.jsx file. We are checking for the user using the useSession() hook. We have a required: true and if we are not authenticated we go back to the home page. So to visit http://localhost:3000/secureclient we must be authenticated.

"use client"

import React from 'react'
import { showErrorToast } from '@/middleware/toastMessage';
import { useRouter } from 'next/navigation';
import { useSession } from "next-auth/react"


const SecureClient = () => {

    const router = useRouter();

    const { status } = useSession({
        required: true,
        onUnauthenticated() {
            showErrorToast("Please login to view your cart");
            router.push('/');
        },
    })


    return (
        <>
            <p> {data?.user?.email} </p>
        </>
    )
}

export default SecureClient

All secure โœ…

Congratulations ๐ŸŽ‰ , you made it. Your app is now secure, without much hassle of writing manual login, serverside APIs, and passing JWT through APIs. That's how easy it is. You can moreover do social logins too.

Thankyou ๐Ÿ’™

If you read or followed it till here, thank you so much. You all keep me motivated throughout. Do follow my Hashnode, for more such blogs. Drop a follow on Twitter for more interesting topics !

Did you find this article valuable?

Support Tamal Das by becoming a sponsor. Any amount is appreciated!

ย