Credentials
To setup Auth.js with external authentication mechanisms or simply use username and password, we need to use the Credentials
provider. 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 Credentials
provider in the Auth.js configuration file. You’ll have to import the provider and add it to your providers
array.
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
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) {
// No user found, so this is their first attempt to login
// meaning this is also the place you could do registration
throw new Error("User not found.")
}
// return user object with the their profile 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 form.
"use client"
import { useState } from "react"
import { signIn } from "@/auth"
export function SignIn() {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
return (
<form
action={async () => {
"use server"
await signIn("credentials", {
// The Credentials provider is non-opinionated. username/password is the simplest use-case,
// but it can be configured to work with almost any other external authentication mechanisms.
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 Credentials
provider use, we can leverage a run-time schema validation library like Zod to validate that the inputs match what we expect.
npm install zod
Next, we’ll setup the schema and parsing in our auth.ts
configuration file, using the authorize
callback on the Credentials
provider.
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"),
})
import NextAuth from "next-auth"
import { ZodError } from "zod"
import Credentials from "next-auth/providers/credentials"
import { signInSchema } from "./lib/zod"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
import { getUserFromDb } from "@/utils/db"
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) options instead of username / password.
However, we also want to be flexible and support anything you deem appropriate for your application and use-case.