Skip to content
Migrating from NextAuth.js v4? Read our migration guide.
Getting Started
Credentials

Credentials

To setup Auth.js with external authentication mechanisms or simply use username and password, we need to use the CredentialsProvider. This provider is designed to forward any credentials inserted into the login form (.i.e username/password) to your authentication service via the authorize callback on the provider configuration.

Credentials Provider

First, lets initialise the CredentialsProvider in the Auth.js configuration file.

./src/auth.ts
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
 
export const { handlers, auth } = NextAuth({
  providers: [
    Credentials({
      // You can specify which fields should be submitted, by adding keys to the `credentials` object.
      // e.g. domain, username, password, 2FA token, etc.
      credentials: {
        email: {},
        password: {},
      },
      authorize: async (credentials) => {
        let user = null
 
        // logic to salt and hash password
        const pwHash = saltAndHashPassword(credentials.password)
 
        // logic to verify if user exists
        user = await getUserFromDb(credentials.email, pwHash)
 
        if (!user) {
          throw new Error("User not found.")
        }
 
        // return json object with the user data
        return user
      },
    }),
  ],
})

If you’re using TypeScript, you can augment the User interface to match the response of your authorize callback, so whenever you read the user in other callbacks (like the jwt) the type will match correctly.

Signin Form

Finally, lets create a simple sign in button.

./components/sign-in.tsx
"use client"
 
import { useState } from "react"
import { signIn } from "../../auth.ts"
 
export function SignIn() {
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  return (
    <form
      action={async () => {
        "use server"
        await signIn("credentials", {
          // Note: The CredentialsProvider is non-opinionated, username/password is the simplest use-case,
          // but can be configured to work with many other authentication mechanisms and inputs.
          email,
          password,
        })
      }}
    >
      <label>
        Email
        <input
          name="email"
          type="email"
          onChange={(e) => setEmail(e.target.value)}
          value={email}
        >
      </label>
      <label>
        Password
        <input
          name="password"
          type="password"
          onChange={(e) => setPassword(e.target.value)}
          value={password}
        >
      </label>
      <button type="submit">Sign In</button>
    </form>
  )
}

Verifying Data with Zod

To improved the security of your CredentialsProvider use, we can leverage a run-time schema validation library like Zod to validate that the inputs match what we expect.

First, we’ll install the Zod dependency.

npm install zod

Next, we’ll setup the schema and parsing in our auth.ts configuration file, using the authorize callback on the CredentialsProvider.

./lib/zod.ts
import { TypeOf, 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"),
})
./auth.ts
import NextAuth from "next-auth"
import { ZodError } from "zod"
import Credentials from "next-auth/providers/credentials"
import { signInSchema } from "./lib/zod"
 
export const { handlers, auth } = NextAuth({
  providers: [
    Credentials({
      // You can specify which fields should be submitted, by adding keys to the `credentials` object.
      // e.g. domain, username, password, 2FA token, etc.
      credentials: {
        email: {},
        password: {},
      },
      authorize: async (credentials) => {
        try {
          let user = null
 
          const { email, password } =
            await signInSchema.parseAsync(credentials)
 
          // logic to salt and hash password
          const pwHash = saltAndHashPassword(password)
 
          // logic to verify if user exists
          user = await getUserFromDb(email, pwHash)
 
          if (!user) {
            throw new Error("User not found.")
          }
 
          // return json object with the user data
          return user
        } catch (error) {
          if (error instanceof ZodError) {
            // Return `null` to indicate that the credentials are invalid
            return null
          }
        }
      },
    }),
  ],
})
⚠️

The industry has come a long way since usernames and passwords were first introduced as the go-to mechanism for authenticating and authorizing users to web applications. Therefore, if possible, we recommend a more modern and secure authentication mechanism such as any of the OAuth providers, Email Magic Links, or WebAuthn (Passkeys) instead of username / password. However, we also want to be flexible and support anything you deem appropriate for your application and use-case.

Auth.js © Balázs Orbán and Team - 2024