RK
Reetesh Kumar@iMBitcoinB

Authentication using Auth.js v5 and Drizzle for Next.js App Router

May 10, 2024

0

11 min read

Authentication in web applications is a crucial part of the user experience. It is essential to have a secure and reliable authentication system in place to protect user data and privacy.

Authentication helps us to identify users and grant them access to specific resources or features based on their identity. And building an authentication system from scratch can be a complex and time-consuming process. You always want your authentication system to be secure, reliable, and easy to use.

What is Auth.JS?#

Auth.JS (formerly Next Auth) is a JavaScript library that provides a simple and secure way to authenticate users in web applications. It's support multiple framework likes Next.JS, Express, Svelte, and more. It's runtime agnostic, so you can use it with any JavaScript runtime environment. It's has built in adapters for popular authentication providers like Google, Facebook, Twitter, and more. as well as built adapters for Prisma, Mongoose, and more.

Before we start, make sure you have a Next.js project set up. We need to install few dependencies to get started.

bash
yarn add next-auth zod nanoid drizzle-orm drizzle-kit dotenv @neondatabase/serverless

We will use Drizzle for the ORM and NeonDatabase for the database. You can use any database of your choice. also for validation we will use Zod. To understand and Learn more about Drizzle and NeonDatabase you can visit this article.

Setting up Drizzle and NeonDatabase#

We will use postgreSQL as our database and Drizzle as our ORM. NeonDatabase is a postgres serverless database so we are going to use it as our database.

We need to quickly create few files and and code to setup the database and ORM.

ts
// db/index.ts
 
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
 
const sql = neon(process.env.NEON_DATABASE_URL!);
const db = drizzle(sql);
 
export default db;
 

Here we have created a db folder with three files index.ts, schema.ts, and drizzle.config.ts. This is all we need to setup Drizzle with NeonDatabase. Now we need to run the migration to create the tables in the database. But drizzle come with built command which work well for development.

bash
npx drizzle-kit generate
 
npx drizzle-kit push

This command is helpful in development since we often need to make changes to the schema and push them to the database. Once you run this command you will see the tables in the database. If not you can check the logs for any errors.

You need to add the NEON_DATABASE_URL in your .env file. You can get the URL from the NeonDatabase dashboard.

Neon DB with Drizzle and Hono in Next.JS

Hono is lightweight, small, simple, and ultrafast web framework for the Edges. Hono makes easy to create Rest API in Next.js app. While Drizzle is Typescript ORM which support edges network out of the box.

Read Full Post
Neon DB with Drizzle and Hono in Next.JS

Setting up Auth.JS#

Auth.JS v5 is a complete rewrite of the library with a focus on simplicity and ease of use. It provides a set of APIs that make it easy to integrate authentication into your application. We will Credentials and OAuth2 provider for the authentication. For OAuth2 provider we will use Google.

ts
   // auth/auth.ts
 
  import db from "@/db";
  import { user } from "@/db/schema";
  import NextAuth, { AuthError } from "next-auth";
  import Credentials from "next-auth/providers/credentials";
  import Google from "next-auth/providers/google";
  import { nanoid } from "nanoid";
 
  export const { handlers, signIn, signOut, auth } = NextAuth({
    providers: [
      Credentials({
        name: "Credentials",
        credentials: {
          email: {},
          id: {},
          name: {},
        },
        authorize: async ({ email, id, name }) => {
          const data = {
            email: email as string,
            id: id as string,
            name: name as string,
          };
          return data;
        },
      }),
      Google({
        clientId: process.env.AUTH_GOOGLE_ID,
        clientSecret: process.env.AUTH_GOOGLE_SECRET,
      }),
    ],
    pages: {
      signIn: "/login",
    },
    callbacks: {
      session: async ({ session, token }) => {
        session.user.id = token.sub!;
        return session;
      },
      signIn: async ({ user: userProvider, account }) => {
        try {
          if (account?.provider === "google") {
            const { image, name, email } = userProvider;
 
            if (!email) {
              throw new AuthError("Failed to sign in");
            }
 
            const isUserExist = (await db.select().from(user)).find(
              (user) => user.email === email
            );
 
            if (!isUserExist) {
               // create password and you mail it to user as temporary password
               // so user can login with email and password too.
                const password = nanoid();
 
                await db
                  .insert(user)
                  .values({
                    name : name as string,
                    email,
                    image : image as string,
                    password,
                  })
                  .returning();
              }
              return true;
          } else if (account?.provider === "credentials") {
            return true;
          }
          return false;
        } catch (error) {
          throw new AuthError("Failed to sign in");
        }
      },
    },
  });
 

We have created three files auth.ts, route.ts, and middleware.ts. In auth.ts we have the Auth.JS configuration. In route.ts we have the route for the Auth.JS. In middleware.ts we have the middleware for the Auth.JS.

Auth.ts is the main file where we have the configuration for the Auth.JS. We have two providers Credentials and Google. We have also added the callbacks for the signIn and session. In the signIn callback we are checking if the user is already in the database or not. If not we are adding the user to the database. You can add more providers and callbacks as per your requirement.

middleware.ts is optional you can use it as per your requirement. But it help Auth.Js to update the session expiry every time whenever its called.

Auth Routes#

We have configure our Databse and Auth.JS. Now we need to create routes for Authentication. We will use server action for Registering user and calling the Auth.JS signIn function.

tsx
// /app/login/page.tsx
 
"use client";
 
import action from './action';
import z from 'zod';
 
const schema = z.object({
email: z.string().email("email is invalid"),
password: z.string().min(6, "Password must be at least 6 characters"),
});
 
const LoginForm = () => {
 
    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
      e.preventDefault();
      try {
        const formData = new FormData(e.currentTarget);
        const email = formData.get("email") as string;
        const password = formData.get("password") as string;
        const schemaResult = schema.safeParse({ email, password });
 
         if (!schemaResult.success) {
           throw new Error(schemaResult.error.errors[0].message);
        }
 
        const res = await action(schemaResult.data);
          if (res && !res?.status) {
            // toast error
            return;
          }
        } catch (error: any) {
          console.error(error.message || "Something went wrong");
        }
    };
 
    return (
    <form onSubmit={handleSubmit} className=" flex flex-col gap-3">
      <Label htmlFor="email">Email</Label>
      <Input id="email" name="email" type="email" />
      <Label htmlFor="password">Password</Label>
      <Input id="password" name="password" type="password" />
      <Button type="submit" className="w-full">
        <span>Login</span>
      </Button>
    </form>
    )};
 
export default LoginForm;
 

We have created two pages login and register for the authentication. We have also created two actions login and register for the authentication. In the login action we are checking if the user is already in the database or not. If the user is in the database we are checking if the password is correct or not. If the password is correct we are calling the signIn function of the Auth.JS. In the register action we are checking if the user is already in the database or not. If the user is not in the database we are adding the user to the database.

These are bare minimum code to get started, You can add more fields and decrypt the password before saving to db for more security.

For OAuth2 provider you can use the signIn function of the Auth.JS. In nextjs with RSC(React Server Components) we can use the form and call our action function. Auth.JS signIn function will take care of the rest.

tsx
 
<form
  action={async () => {
    "use server";
    await signIn("google", {
      redirect: true,
      redirectTo: "/", // as per your requirement
    });
  }}
>
  <button>
    <
    Sign in with Google
  </button>
</form>
 

Now you must be able to login and register the user. You can always visit to Auth.JS documentation for more information. Now once you register user and login you will be get the user data in the session. You can use this data to show the user profile or any other information.

tsx
// /app/page.tsx
 
import { auth } from '@/auth/auth';
 
const HomePage = async () => {
  const user = await auth();
 
  return <h1>{JSON.stringify(user)}</h1>;
};

You can find the complete code in the GitHub repository.You can always clone the repository and move to auth branch and run the code to see the working example. You just need to add the .env file with the required variables.

Conclusion#

Auth.JS v5 with latest changes has made too easy to manges authentication specially in Next.JS apps with RSC also playes major role in this and this verison is updated keeping mind of Next.JS APP Router.

I like Auth.JS over other auth libaray because of its simplicity and easy to use. It's support multiple framework and runtime. It's just abstract the complexity and the same time gives us full control and flexibility to custimize as per our requirement.

I hope this article helps you to understand how to use Auth.JS v5 with Drizzle for authentication in a Next.js application. You can always comment below if you have any questions or feedback. Happy coding!

Comments (3)

Related Posts

Made with ❤️ by Reetesh