Next.js - Authentication



What is Authentication?

Authentication is the process of verifying the identity of a user. It is used to ensure that the user is who they claim to be. In web applications, authentication is done by verifying the user's credentials, such as username and password. The authentication process has following three steps:

  • Take user's credentials
  • Validate the credentials
  • Verify the credentials with database

Authentication in Next.js

In Next.js, authentication of a user is done in three steps. The section below explains each steps of the authentication in Next.js.

Create a Next.js Form

In web applications, forms are used to take credentials from user. You can use Next.js <Form> component to create an applications form in Next.js. The <Form> extends functionality of HTML form tag and provide extra features for client-side navigation on submission, prefetching of loading UI, and progressive enhancement.

In the code below, we are creating a simple form with email and password fields.

// app/ui/signup-form.tsx

import { signup } from '@/app/actions/auth'
 
export function SignupForm() {
  return (
    <Form action={signup}>
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" name="email" type="email" placeholder="Email" />
      </div>
      <div>
        <label htmlFor="password">Password</label>
        <input id="password" name="password" type="password" />
      </div>
      <button type="submit">Sign Up</button>
    </Form>
  )
}

Validate Form Fields on the Server

Form validations are used to ensure that the data entered by the user is in valid format. This step will prevent unwanted data from being submitted to the server and reduce the server load.

Next.js provides server actions to validate the form fields on the server. You can use schema validation library like Zod or Yup to validate the form fields. In the code below, we are using Zod to validate the form fields.

// app/actions/auth.ts

import { z } from 'zod'
 
export const SignupFormSchema = z.object({
  email: z.string().email({ message: 'Enter a valid email.' }).trim(),
  password: z
    .string()
    .regex(/[a-zA-Z]/, { message: 'Contain at least one letter.' })
    .regex(/[0-9]/, { message: 'Contain at least one number.' })
    .regex(/[^a-zA-Z0-9]/, {
      message: 'Contain at least one special character.',
    })
    .trim(),
})
 
export type FormState =
  | {
      errors?: {
        name?: string[]
        email?: string[]
        password?: string[]
      }
      message?: string
    }
  | undefined

Check User Credentials

After validating the user entries, next step is to check the user credentials with the database. Based on this information, you can create a new user account or sign in the user. See the cod e below

export async function signup(state: FormState, formData: FormData) {
    // 1. Validate form fields
    // ...
    
    // 2. Prepare data for insertion into database
    const { name, email, password } = validatedFields.data
    // e.g. Hash the user's password before storing it
    const hashedPassword = await bcrypt.hash(password, 10)
    
    // 3. Insert the user into the database or call an Auth Library's API
    const data = await db
        .insert(users)
        .values({
        name,
        email,
        password: hashedPassword,
        })
        .returning({ id: users.id })
    
    const user = data[0]
    
    if (!user) {
        return {
        message: 'An error occurred while creating your account.',
        }
    }
    
    // TODO:
    // 4. Create user session
    // 5. Redirect user
}
Advertisements