前端转全栈 07:认证与授权——Supabase Auth、GitHub OAuth 与 RLS

前端全栈tutorialsupabaseauthoauthrls认证

系列目录:本文是「前端转全栈:Next.js + Supabase 实战」系列的第 7 篇。前面接口都假设「用户已登录」,这一篇真正把用户系统做出来。


「认证」和「授权」常被混为一谈,先厘清:

  • 认证(Authentication, AuthN):你是谁?→ 登录。
  • 授权(Authorization, AuthZ):你能干啥?→ 权限。

漏掉任一个都出大事:只认证不授权 → 普通用户能删别人数据;只授权不认证 → 根本不知道是谁在操作。


一、Supabase Auth:开箱即用的用户系统

本站用 Supabase Auth,你不用自己建用户表、不用写密码哈希(密码安全是地狱级难题,千万别自己造轮子)。它自带邮箱、GitHub、Google 等登录方式。

GitHub 登录只需前端一行触发:

// 浏览器端
const supabase = createClient() // src/lib/supabase/client.ts
await supabase.auth.signInWithOAuth({
  provider: "github",
  options: { redirectTo: `${location.origin}/auth/callback` },
})

二、本站真实的登录回调

OAuth 会跳回 redirectTo 指定的地址,并带上 code。本站 src/app/auth/callback/route.ts 负责「用 code 换会话」:

// src/app/auth/callback/route.ts
import { NextResponse } from "next/server"
import { createClient } from "@/lib/supabase/server"

export async function GET(request: NextRequest) {
  const { searchParams, origin } = new URL(request.url)
  const code = searchParams.get("code")
  const next = searchParams.get("next") ?? "/"
  if (code) {
    const supabase = await createClient()
    const { error } = await supabase.auth.exchangeCodeForSession(code)
    if (!error) return NextResponse.redirect(`${origin}${next}`)
  }
  return NextResponse.redirect(`${origin}/?error=auth_error`)
}

流程:GitHub 给 code → 服务端 exchangeCodeForSession 换成会话 token 并写入 cookie → 后续请求服务端自动识别用户(呼应第 2 篇的 cookie 机制)。

这就是第 4 篇 route.tssupabase.auth.getUser() 能拿到用户的原因——token 在 cookie 里,服务端客户端都读得到。


三、会话 vs JWT(概念扫盲)

  • Session(本站方案):服务端存会话,浏览器只持一个随机 ID(cookie)。好处是「可随时吊销」,坏处是要服务端存储。
  • JWT:服务端签一张「带签名的通行证」发给浏览器,之后每次请求自带,服务端不存。好处是无状态、适合分布式;坏处是签发后难即时吊销。

Supabase 用的是 JWT 形式的 access token,但由它托管刷新逻辑,你基本无感。初学直接用 Supabase Auth 即可,别自己实现 JWT


四、RLS:在数据层锁死权限(最关键的防线)

光在接口里写 if (!user) return 401 不够——万一哪天忘了写,数据库就裸奔。RLS(Row Level Security,行级安全) 把权限直接焊死在数据层:即使绕过接口直连,规则依然生效。

本站 supabase/schema.sql 的策略:

ALTER TABLE comments ENABLE ROW LEVEL SECURITY;

-- 任何人都能读
CREATE POLICY "Anyone can read comments"
  ON comments FOR SELECT USING (true);

-- 只有登录用户能插入
CREATE POLICY "Authenticated users can insert comments"
  ON comments FOR INSERT
  WITH CHECK (auth.role() = 'authenticated');

-- 只能删自己的
CREATE POLICY "Users can delete their own comments"
  ON comments FOR DELETE
  USING (auth.uid() = user_id);

auth.uid() 是 Supabase 提供的函数,返回当前登录用户 ID。USING 控制「能读/删哪些行」,WITH CHECK 控制「能插入/改哪些行」

黄金法则:接口做校验是体验层,RLS 是安全层,两者都要有。接口挂了 RLS 还在,RLS 是最后一道不可绕过的墙。


五、别忘了「注销」

await supabase.auth.signOut() // 清掉 cookie 会话

很多新手只做登录不做注销,导致「换个用户」还是旧身份。


总结

  • 认证 ≠ 授权,两者缺一不可。
  • Supabase Auth 直接给登录 + 会话,别自己写密码哈希。
  • OAuth 回调用 code 换会话,会话存 cookie。
  • RLS 是数据层最后防线,用 USING / WITH CHECK 把权限焊死。
  • 接口校验 + RLS 双保险。

下一篇,我们把「提交表单」这最常见的交互做成服务端能力——表单与服务端 Action


练习:在 Supabase 后台开启 GitHub 登录(填好 GitHub OAuth App 的 Client ID/Secret),本地跑通 signInWithOAuth → 回调 → getUser() 拿到用户,体验完整的「你是谁」闭环。

Comments

Sign in to leave a comment.