Files
myeasycms-v2/apps/web/content/documentation/authentication/email-password.mdoc
Giancarlo Buomprisco 7ebff31475 Next.js Supabase V3 (#463)
Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
2026-03-24 13:40:38 +08:00

383 lines
7.9 KiB
Plaintext

---
title: "Email & Password"
description: "Traditional email and password authentication."
publishedAt: 2024-04-11
order: 2
status: "published"
---
> **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
```typescript
import { signUpAction } from '~/lib/auth/actions';
const result = await signUpAction({
email: 'user@example.com',
password: 'SecurePassword123!',
});
```
### Server Action Implementation
```typescript
'use server';
import { enhanceAction } from '@kit/next/actions';
import * as z from 'zod';
const SignUpSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export const signUpAction = enhanceAction(
async (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 };
},
{ schema: SignUpSchema }
);
```
### Sign Up Component
```tsx
'use client';
import { useForm } from 'react-hook-form';
import { signUpAction } from '../_lib/actions';
export function SignUpForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = async (data) => {
const result = await signUpAction(data);
if (result.success) {
toast.success('Check your email to confirm your account');
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<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">Sign Up</button>
</form>
);
}
```
## Sign In Flow
### User Login
```typescript
export const signInAction = enhanceAction(
async (data) => {
const client = getSupabaseServerClient();
const { error } = await client.auth.signInWithPassword({
email: data.email,
password: data.password,
});
if (error) throw error;
redirect('/home');
},
{ schema: SignInSchema }
);
```
### Sign In Component
```tsx
'use client';
export function SignInForm() {
const { register, handleSubmit } = useForm();
const onSubmit = async (data) => {
try {
await signInAction(data);
} catch (error) {
toast.error('Invalid email or password');
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="email"
{...register('email')}
placeholder="Email"
/>
<input
type="password"
{...register('password')}
placeholder="Password"
/>
<button type="submit">Sign In</button>
</form>
);
}
```
## Email Verification
### Requiring Email Confirmation
Configure in Supabase dashboard or config:
```typescript
// config/auth.config.ts
export const authConfig = {
requireEmailConfirmation: true,
};
```
### Handling Unconfirmed Emails
```typescript
export const signInAction = enhanceAction(
async (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');
},
{ schema: SignInSchema }
);
```
## Password Reset
### Request Password Reset
```typescript
export const requestPasswordResetAction = enhanceAction(
async (data) => {
const client = getSupabaseServerClient();
const { error } = await client.auth.resetPasswordForEmail(data.email, {
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/reset-password`,
});
if (error) throw error;
return {
success: true,
message: 'Check your email for reset instructions',
};
},
{
schema: z.object({
email: z.string().email(),
}),
}
);
```
### Reset Password Form
```tsx
'use client';
export function PasswordResetRequestForm() {
const { register, handleSubmit } = useForm();
const onSubmit = async (data) => {
const result = await requestPasswordResetAction(data);
if (result.success) {
toast.success(result.message);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="email"
{...register('email')}
placeholder="Enter your email"
/>
<button type="submit">Send Reset Link</button>
</form>
);
}
```
### Update Password
```typescript
export const updatePasswordAction = enhanceAction(
async (data) => {
const client = getSupabaseServerClient();
const { error } = await client.auth.updateUser({
password: data.newPassword,
});
if (error) throw error;
redirect('/home');
},
{
schema: z.object({
newPassword: z.string().min(8),
}),
}
);
```
## Password Requirements
### Validation Schema
```typescript
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
```tsx
'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
```typescript
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
```typescript
export const signOutAction = enhanceAction(
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