Published on

Auth.js: Authentication in Next.js

Authors
  • avatar
    Name
    Shelton Ma
    Twitter

Configuration in Auth.js

  1. Setting Up auth

    // src/auth.ts
    import NextAuth from "next-auth";
    import authConfig from "./auth.config";
    
    export const { handlers, signIn, signOut, auth } = NextAuth(authConfig);
    
  2. Configuring auth.config

    // src/auth.config.ts
    export default {
      providers: [
        GitHub,
      ]
      adapter: DrizzleAdapter(db),
    } satisfies NextAuthConfig;
    

OAuth

Github OAuth

  1. Environment Variables

    // .env.local
    AUTH_GITHUB_ID=
    AUTH_GITHUB_SECRET=
    
  2. Enable the Provider

    // src/auth.config.ts
    providers: [
      GitHub,
    ]
    
  3. SignIn

    // src/features/auth/components/github-button.tsx
    import { signIn } from "@/auth";
    
    export function GithubButton() {
      return (
        <form
          action={async () => {
            "use server";
            await signIn("github");
          }}
        >
          <button type="submit">Signin with GitHub</button>
        </form>
      );
    }
    

Credentials

0. Utilities

  1. Hash Password

    // src/utils/password.ts
    import bcrypt from "bcryptjs";
    
    export const saltAndHashPassword = (password: string) => {
      return bcrypt.hashSync(password, 10);
    };
    
    export const verifyPassword = (password: string, hashedPassword: string) => {
      return bcrypt.compareSync(password, hashedPassword);
    };
    
  2. Database utility

    // src/utils/db.ts
    import { db } from "@/db/drizzle";
    import { users } from "@/db/schema";
    import { and, eq } from "drizzle-orm";
    
    export const getUserFromDb = async (email: string, password: string) => {
      const user = await db
        .select()
        .from(users)
        .where(and(eq(users.email, email), eq(users.password, password)));
      return user.length > 0 ? user[0] : null;
    };
    
  3. Zod Validation Schema

    // src/lib/zod.ts
    import { object, string } from "zod";
    
    export const signInSchema = object({
      email: string({ required_error: "Email is required" })
        .min(1, "Email is required")
        .email("Invalid email"),
      password: string({ required_error: "Password is required" })
        .min(1, "Password is required")
        .min(8, "Password must be more than 8 characters")
        .max(32, "Password must be less than 32 characters"),
    });
    

1. provider

// src/auth.config.ts
import { signInSchema } from "@/lib/zod";
import { getUserFromDb } from "@/utils/db";
import { saltAndHashPassword } from "@/utils/password";
import Credentials from "next-auth/providers/credentials";

providers: [
  Credentials({
    credentials: {
      email: { label: "Email", type: "email" },
      password: { label: "Password", type: "password" },
    },
    authorize: async (credentials) => {
      let user = null;

      const validatedFields = signInSchema.safeParse(credentials);

      if (!validatedFields.success) {
        throw new Error("Invalid credentials.");
      }

      const { email, password } = validatedFields.data;
      const pwHash = saltAndHashPassword(password);

      user = await getUserFromDb(email, pwHash);

      if (!user) {
        throw new Error("Invalid credentials.");
      }
      return user;
    },
  }),
],

2. Signin Form

// src/features/auth/components/credential-form.tsx
import { signIn } from "@/auth";

export const CredentialForm = () => {
  return (
    <form
      action={async (formData) => {
        "use server";
        await signIn("credentials", formData);
      }}
    >
      <label>
        Email
        <input name="email" type="email" />
      </label>
      <label>
        Password
        <input name="password" type="password" />
      </label>
      <button>Sign In</button>
    </form>
  );
};