Supabase邮箱密码认证 #

一、用户注册 #

1.1 基础注册 #

typescript
// 用户注册
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
})

// 注册结果
// data.user: 用户信息
// data.session: 会话信息(如果自动登录)
// error: 错误信息

1.2 带元数据注册 #

typescript
// 注册时添加用户信息
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
  options: {
    data: {
      full_name: 'John Doe',
      avatar_url: 'https://example.com/avatar.jpg',
    },
    emailRedirectTo: 'https://example.com/welcome',
  },
})

1.3 注册流程 #

text
注册流程
├── 1. 用户提交邮箱和密码
├── 2. Supabase验证格式
├── 3. 检查邮箱是否已注册
├── 4. 创建用户记录
├── 5. 发送验证邮件(可选)
├── 6. 返回用户信息
└── 7. 自动登录(可选)

1.4 注册表单示例 #

tsx
import { useState } from 'react'
import { supabase } from '../lib/supabase'

export function SignUpForm() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [loading, setLoading] = useState(false)
  const [message, setMessage] = useState('')

  const handleSignUp = async (e: React.FormEvent) => {
    e.preventDefault()
    setLoading(true)
    setMessage('')

    const { data, error } = await supabase.auth.signUp({
      email,
      password,
      options: {
        data: {
          full_name: email.split('@')[0],
        },
      },
    })

    setLoading(false)

    if (error) {
      setMessage(error.message)
    } else if (data.user && !data.session) {
      setMessage('请查收验证邮件完成注册')
    } else {
      setMessage('注册成功!')
    }
  }

  return (
    <form onSubmit={handleSignUp}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        minLength={6}
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Loading...' : 'Sign Up'}
      </button>
      {message && <p>{message}</p>}
    </form>
  )
}

二、用户登录 #

2.1 基础登录 #

typescript
// 用户登录
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password',
})

// 登录结果
// data.user: 用户信息
// data.session: 会话信息

2.2 登录表单示例 #

tsx
import { useState } from 'react'
import { supabase } from '../lib/supabase'

export function SignInForm() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')

  const handleSignIn = async (e: React.FormEvent) => {
    e.preventDefault()
    setLoading(true)
    setError('')

    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password,
    })

    setLoading(false)

    if (error) {
      setError(error.message)
    } else {
      // 登录成功,跳转
      window.location.href = '/dashboard'
    }
  }

  return (
    <form onSubmit={handleSignIn}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Loading...' : 'Sign In'}
      </button>
      {error && <p className="error">{error}</p>}
    </form>
  )
}

三、邮箱验证 #

3.1 配置邮箱验证 #

text
Dashboard > Authentication > Providers > Email

配置选项
├── Enable email confirmations
│   └── 开启后注册需要验证邮箱
│
├── Secure email change
│   └── 更改邮箱时需要验证
│
└── Secure password change
    └── 更改密码时需要验证

3.2 验证邮件回调 #

typescript
// 处理验证邮件回调
// URL: https://example.com/auth/callback?token=xxx&type=signup

import { useEffect } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { supabase } from '../lib/supabase'

export function AuthCallback() {
  const [searchParams] = useSearchParams()
  const navigate = useNavigate()

  useEffect(() => {
    const token = searchParams.get('token')
    const type = searchParams.get('type')

    if (token && type) {
      supabase.auth.verifyOtp({
        token_hash: token,
        type: type as any,
      }).then(({ error }) => {
        if (error) {
          console.error('Verification failed:', error)
          navigate('/auth/error')
        } else {
          navigate('/dashboard')
        }
      })
    }
  }, [searchParams, navigate])

  return <div>Verifying...</div>
}

3.3 重新发送验证邮件 #

typescript
// 重新发送验证邮件
const { error } = await supabase.auth.resend({
  type: 'signup',
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://example.com/auth/callback',
  },
})

四、密码重置 #

4.1 发送重置邮件 #

typescript
// 发送密码重置邮件
const { data, error } = await supabase.auth.resetPasswordForEmail(
  'user@example.com',
  {
    redirectTo: 'https://example.com/reset-password',
  }
)

4.2 重置密码页面 #

tsx
import { useState } from 'react'
import { supabase } from '../lib/supabase'

export function ResetPasswordForm() {
  const [password, setPassword] = useState('')
  const [confirmPassword, setConfirmPassword] = useState('')
  const [loading, setLoading] = useState(false)
  const [message, setMessage] = useState('')

  const handleResetPassword = async (e: React.FormEvent) => {
    e.preventDefault()
    
    if (password !== confirmPassword) {
      setMessage('Passwords do not match')
      return
    }

    setLoading(true)

    const { error } = await supabase.auth.updateUser({
      password: password,
    })

    setLoading(false)

    if (error) {
      setMessage(error.message)
    } else {
      setMessage('Password updated successfully!')
    }
  }

  return (
    <form onSubmit={handleResetPassword}>
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="New Password"
        minLength={6}
        required
      />
      <input
        type="password"
        value={confirmPassword}
        onChange={(e) => setConfirmPassword(e.target.value)}
        placeholder="Confirm Password"
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Updating...' : 'Reset Password'}
      </button>
      {message && <p>{message}</p>}
    </form>
  )
}

4.3 处理重置回调 #

typescript
// 处理密码重置回调
// URL: https://example.com/reset-password?token=xxx&type=recovery

useEffect(() => {
  const hash = window.location.hash
  if (hash && hash.includes('type=recovery')) {
    // 用户点击了重置链接,显示重置表单
    setShowResetForm(true)
  }
}, [])

五、用户登出 #

5.1 登出当前设备 #

typescript
// 登出当前会话
const { error } = await supabase.auth.signOut()

5.2 登出所有设备 #

typescript
// 登出所有设备
const { error } = await supabase.auth.signOut({ scope: 'global' })

5.3 登出示例 #

tsx
import { supabase } from '../lib/supabase'

export function SignOutButton() {
  const handleSignOut = async () => {
    const { error } = await supabase.auth.signOut()
    
    if (error) {
      console.error('Sign out error:', error)
    } else {
      window.location.href = '/login'
    }
  }

  return (
    <button onClick={handleSignOut}>
      Sign Out
    </button>
  )
}

六、获取用户信息 #

6.1 获取当前用户 #

typescript
// 获取当前用户
const { data: { user }, error } = await supabase.auth.getUser()

if (user) {
  console.log('User ID:', user.id)
  console.log('Email:', user.email)
  console.log('Metadata:', user.user_metadata)
}

6.2 获取会话 #

typescript
// 获取当前会话
const { data: { session }, error } = await supabase.auth.getSession()

if (session) {
  console.log('Access Token:', session.access_token)
  console.log('Refresh Token:', session.refresh_token)
  console.log('Expires At:', session.expires_at)
}

6.3 监听认证状态 #

typescript
// 监听认证状态变化
const { data: { subscription } } = supabase.auth.onAuthStateChange(
  (event, session) => {
    if (event === 'SIGNED_IN') {
      console.log('User signed in:', session?.user)
    }
    if (event === 'SIGNED_OUT') {
      console.log('User signed out')
    }
  }
)

// 清理监听
subscription.unsubscribe()

七、更新用户信息 #

7.1 更新用户元数据 #

typescript
// 更新用户元数据
const { data, error } = await supabase.auth.updateUser({
  data: {
    full_name: 'John Doe',
    avatar_url: 'https://example.com/new-avatar.jpg',
  },
})

7.2 更新邮箱 #

typescript
// 更新邮箱
const { data, error } = await supabase.auth.updateUser({
  email: 'new-email@example.com',
})

7.3 更新密码 #

typescript
// 更新密码(需要当前会话)
const { data, error } = await supabase.auth.updateUser({
  password: 'new-secure-password',
})

八、服务端操作 #

8.1 管理员创建用户 #

typescript
import { createClient } from '@supabase/supabase-js'

const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!,
  {
    auth: {
      autoRefreshToken: false,
      persistSession: false,
    },
  }
)

// 管理员创建用户
const { data, error } = await supabaseAdmin.auth.admin.createUser({
  email: 'admin@example.com',
  password: 'secure-password',
  email_confirm: true,  // 自动确认邮箱
  user_metadata: {
    role: 'admin',
  },
})

8.2 管理员列出用户 #

typescript
// 列出所有用户
const { data, error } = await supabaseAdmin.auth.admin.listUsers()

// 分页列出
const { data, error } = await supabaseAdmin.auth.admin.listUsers({
  page: 1,
  perPage: 50,
})

8.3 管理员删除用户 #

typescript
// 删除用户
const { error } = await supabaseAdmin.auth.admin.deleteUser(userId)

九、错误处理 #

9.1 常见错误码 #

typescript
const errorMessages: Record<string, string> = {
  'invalid_credentials': '邮箱或密码错误',
  'email_not_confirmed': '请先验证邮箱',
  'user_already_exists': '该邮箱已注册',
  'weak_password': '密码强度不足',
  'invalid_email': '邮箱格式不正确',
  'user_not_found': '用户不存在',
  'session_not_found': '会话已过期,请重新登录',
}

function getErrorMessage(error: any): string {
  return errorMessages[error.message] || error.message
}

9.2 错误处理示例 #

typescript
async function handleAuth() {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  })

  if (error) {
    switch (error.message) {
      case 'invalid_credentials':
        return '邮箱或密码错误'
      case 'email_not_confirmed':
        return '请先验证邮箱'
      default:
        return error.message
    }
  }

  return data
}

十、安全建议 #

10.1 密码策略 #

typescript
// 密码验证函数
function validatePassword(password: string): { valid: boolean; message: string } {
  if (password.length < 8) {
    return { valid: false, message: '密码至少8个字符' }
  }
  if (!/[A-Z]/.test(password)) {
    return { valid: false, message: '密码需要包含大写字母' }
  }
  if (!/[a-z]/.test(password)) {
    return { valid: false, message: '密码需要包含小写字母' }
  }
  if (!/[0-9]/.test(password)) {
    return { valid: false, message: '密码需要包含数字' }
  }
  return { valid: true, message: '' }
}

10.2 防暴力破解 #

text
Supabase内置保护
├── 登录失败次数限制
├── IP限制
├── 验证码保护
└── 速率限制

十一、总结 #

邮箱密码认证要点:

操作 方法
注册 signUp({ email, password })
登录 signInWithPassword({ email, password })
登出 signOut()
获取用户 getUser()
更新用户 updateUser({ … })
重置密码 resetPasswordForEmail(email)

下一步,让我们学习OAuth社交登录!

最后更新:2026-03-28