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