Email & Password

Traditional email and password authentication.

Note: This is mock/placeholder content for demonstration purposes.

Email and password authentication is the traditional way users sign up and sign in.

Overview

Email/password authentication provides:

  • User registration with email verification
  • Secure password storage
  • Password reset functionality
  • Session management

Sign Up Flow

User Registration

Call the sign-up action through useAction in the client component shown below so pending, success, and error states stay consistent.

Server Action Implementation

'use server';

import {
  authActionClient,
  publicActionClient,
} from '@kit/next/safe-action';
import * as z from 'zod';

const SignUpSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export const signUpAction = publicActionClient
  .inputSchema(SignUpSchema)
  .action(async ({ parsedInput: data }) => {
    const client = getSupabaseServerClient();

    const { data: authData, error } = await client.auth.signUp({
      email: data.email,
      password: data.password,
      options: {
        emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
      },
    });

    if (error) throw error;

    return { success: true, data: authData };
  });

Sign Up Component

'use client';

import { useAction } from 'next-safe-action/hooks';
import { useForm } from 'react-hook-form';
import { signUpAction } from '../_lib/actions';

export function SignUpForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();
  const { execute, isPending } = useAction(signUpAction, {
    onSuccess: () => {
      toast.success('Check your email to confirm your account');
    },
    onError: () => {
      toast.error('Could not create your account');
    },
  });

  return (
    <form onSubmit={handleSubmit((data) => execute(data))}>
      <div>
        <label>Email</label>
        <input
          type="email"
          {...register('email', { required: true })}
        />
        {errors.email && <span>Email is required</span>}
      </div>

      <div>
        <label>Password</label>
        <input
          type="password"
          {...register('password', { required: true, minLength: 8 })}
        />
        {errors.password && <span>Password must be 8+ characters</span>}
      </div>

      <button type="submit" disabled={isPending}>
        {isPending ? 'Creating account...' : 'Sign Up'}
      </button>
    </form>
  );
}

Sign In Flow

User Login

const SignInSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export const signInAction = publicActionClient
  .inputSchema(SignInSchema)
  .action(async ({ parsedInput: data }) => {
    const client = getSupabaseServerClient();

    const { error } = await client.auth.signInWithPassword({
      email: data.email,
      password: data.password,
    });

    if (error) throw error;

    redirect('/home');
  });

Sign In Component

'use client';

import { useAction } from 'next-safe-action/hooks';

export function SignInForm() {
  const { register, handleSubmit } = useForm();
  const { execute, isPending } = useAction(signInAction, {
    onError: () => {
      toast.error('Invalid email or password');
    },
  });

  return (
    <form onSubmit={handleSubmit((data) => execute(data))}>
      <input
        type="email"
        {...register('email')}
        placeholder="Email"
      />
      <input
        type="password"
        {...register('password')}
        placeholder="Password"
      />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Signing in...' : 'Sign In'}
      </button>
    </form>
  );
}

Email Verification

Requiring Email Confirmation

Configure in Supabase dashboard or config:

// config/auth.config.ts
export const authConfig = {
  requireEmailConfirmation: true,
};

Handling Unconfirmed Emails

export const signInAction = publicActionClient
  .inputSchema(SignInSchema)
  .action(async ({ parsedInput: data }) => {
    const client = getSupabaseServerClient();

    const { data: authData, error } = await client.auth.signInWithPassword({
      email: data.email,
      password: data.password,
    });

    if (error) {
      if (error.message.includes('Email not confirmed')) {
        return {
          success: false,
          error: 'Please confirm your email before signing in',
        };
      }
      throw error;
    }

    redirect('/home');
  });

Password Reset

Request Password Reset

const PasswordResetRequestSchema = z.object({
  email: z.string().email(),
});

export const requestPasswordResetAction = publicActionClient
  .inputSchema(PasswordResetRequestSchema)
  .action(async ({ parsedInput: data }) => {
    const client = getSupabaseServerClient();

    const { error } = await client.auth.resetPasswordForEmail(data.email, {
      redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback?next=/update-password`,
    });

    if (error) throw error;

    return {
      success: true,
      message: 'Check your email for reset instructions',
    };
  });

Reset Password Form

'use client';

import { useAction } from 'next-safe-action/hooks';

export function PasswordResetRequestForm() {
  const { register, handleSubmit } = useForm();
  const { execute, isPending } = useAction(requestPasswordResetAction, {
    onSuccess: ({ data }) => {
      toast.success(data?.message ?? 'Check your email');
    },
    onError: () => {
      toast.error('Could not send the reset link');
    },
  });

  return (
    <form onSubmit={handleSubmit((data) => execute(data))}>
      <input
        type="email"
        {...register('email')}
        placeholder="Enter your email"
      />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Sending...' : 'Send Reset Link'}
      </button>
    </form>
  );
}

Update Password

const UpdatePasswordSchema = z.object({
  newPassword: z.string().min(8),
});

export const updatePasswordAction = authActionClient
  .inputSchema(UpdatePasswordSchema)
  .action(async ({ parsedInput: data }) => {
    const client = getSupabaseServerClient();

    const { error } = await client.auth.updateUser({
      password: data.newPassword,
    });

    if (error) throw error;

    redirect('/home');
  });

Password Requirements

Validation Schema

const PasswordSchema = z
  .string()
  .min(8, 'Password must be at least 8 characters')
  .regex(/[A-Z]/, 'Password must contain an uppercase letter')
  .regex(/[a-z]/, 'Password must contain a lowercase letter')
  .regex(/[0-9]/, 'Password must contain a number')
  .regex(/[^A-Za-z0-9]/, 'Password must contain a special character');

Password Strength Indicator

'use client';

import { useState } from 'react';

export function PasswordInput() {
  const [password, setPassword] = useState('');
  const strength = calculatePasswordStrength(password);

  return (
    <div>
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <div className="flex gap-1">
        {[1, 2, 3, 4].map((level) => (
          <div
            key={level}
            className={cn(
              'h-1 flex-1 rounded',
              strength >= level ? 'bg-green-500' : 'bg-gray-200'
            )}
          />
        ))}
      </div>
      <span className="text-sm">
        {strength === 4 && 'Strong password'}
        {strength === 3 && 'Good password'}
        {strength === 2 && 'Fair password'}
        {strength === 1 && 'Weak password'}
      </span>
    </div>
  );
}

Session Management

Checking Authentication Status

import { getSupabaseServerClient } from '@kit/supabase/server-client';

export async function requireAuth() {
  const client = getSupabaseServerClient();
  const { data: { user } } = await client.auth.getUser();

  if (!user) {
    redirect('/auth/sign-in');
  }

  return user;
}

Sign Out

export const signOutAction = authActionClient.action(async () => {
  const client = getSupabaseServerClient();
  await client.auth.signOut();
  redirect('/auth/sign-in');
});

Security Best Practices

  1. Enforce strong passwords - Minimum 8 characters, mixed case, numbers, symbols
  2. Rate limit login attempts - Prevent brute force attacks
  3. Use HTTPS only - Encrypt data in transit
  4. Enable email verification - Confirm email ownership
  5. Implement account lockout - After failed attempts
  6. Log authentication events - Track sign-ins and failures
  7. Support 2FA - Add extra security layer