Weadb Notes

arctic 教程

刚刚·8 min read

arctic 是一个现代、轻量级且类型安全的 JavaScript/TypeScript OAuth 2.0 客户端库(由 Lucia Auth 的作者开发)。它不绑定任何特定的框架(如 Next.js 或 SvelteKit),也不绑定特定的数据库,只专注于处理 OAuth 的协议流程。

下面以 Google 账号登录 为例,详细介绍如何使用 arctic 实现完整的 OAuth 2.0 登录流程。


1. 准备工作:在 Google Cloud 获取凭证

在开始写代码前,你需要在 Google Cloud Console 中配置:

  1. 创建一个项目并启用 OAuth 同意屏幕 (OAuth consent screen)
  2. 进去 凭据 (Credentials) 页面,创建一个 OAuth 2.0 客户端 ID (OAuth 2.0 Client ID)
  3. 设置 已启用的重定向 URI (Authorized redirect URIs)。例如本地开发可以设置为: http://localhost:3000/login/google/callback
  4. 保存后,你将获得:
    • Client ID (客户端 ID)
    • Client Secret (客户端密钥)

2. 安装 arctic

在你的项目中安装 arctic

npm install arctic

3. 初始化 Google 客户端

在项目中创建一个 OAuth 配置文件(例如 src/lib/oauth.ts),初始化 Google 实例:

import { Google } from "arctic";

// 建议将敏感信息放在环境变量中
const clientId = process.env.GOOGLE_CLIENT_ID!;
const clientSecret = process.env.GOOGLE_CLIENT_SECRET!;
const redirectURI = "http://localhost:3000/login/google/callback";

export const google = new Google(clientId, clientSecret, redirectURI);

4. 步骤一:引导用户跳转至 Google 登录页

当用户点击“使用 Google 登录”按钮时,你需要生成一个授权 URL,并重定向用户。 为了防止 CSRF 攻击,Google 登录强制要求使用 State(状态码)和 Code Verifier(PKCE 代码验证器)。

以下是服务端的处理逻辑(以标准 Web API 规范为例,适用于 Next.js, SvelteKit, Astro 等):

import { generateState, generateCodeVerifier } from "arctic";
import { google } from "./oauth";

export async function GET(request: Request) {
  // 1. 生成安全的随机字符
  const state = generateState();
  const codeVerifier = generateCodeVerifier();

  // 2. 生成 Google 授权 URL
  const scopes = ["openid", "profile", "email"];
  const url = await google.createAuthorizationURL(state, codeVerifier, scopes);

  // 3. 将 state 和 codeVerifier 存入 Cookie 中,供回调阶段校验
  // 注意:生产环境务必开启 Secure, HttpOnly 并设置合适的 SameSite 属性
  const response = new Response(null, {
    status: 302,
    headers: {
      Location: url.toString(),
    },
  });

  // 存入 cookie (通常需要库如 serialize 或框架自带的 cookie 挂载)
  // 示例伪代码:
  // setCookie("google_oauth_state", state, { secure: true, httpOnly: true, maxAge: 600 });
  // setCookie("google_code_verifier", codeVerifier, { secure: true, httpOnly: true, maxAge: 600 });

  return response;
}

5. 步骤二:处理 Google 回调 (Callback)

用户在 Google 页面同意授权后,浏览器会被重定向回你设置的 redirectURI(例如 /login/google/callback),并携带 codestate 参数。

你需要在这个路由中接收参数,校验它们,并向 Google 换取用户信息。

import { google } from "./oauth";

export async function GET(request: Request) {
  const url = new URL(request.url);
  const code = url.searchParams.get("code");
  const state = url.searchParams.get("state");

  // 1. 从之前保存的 Cookie 中取出 state 和 codeVerifier
  const storedState = getCookie(request, "google_oauth_state");
  const storedCodeVerifier = getCookie(request, "google_code_verifier");

  // 2. 安全校验:确保 state 匹配以防止 CSRF 攻击
  if (!code || !state || !storedState || state !== storedState || !storedCodeVerifier) {
    return new Response("参数校验失败,请重新登录", { status: 400 });
  }

  try {
    // 3. 使用授权码和 codeVerifier 向 Google 换取 Token
    const tokens = await google.validateAuthorizationCode(code, storedCodeVerifier);
    
    // tokens 对象中包含:accessToken, idToken, accessTokenExpiresAt 等
    const accessToken = tokens.accessToken(); 

    // 4. 使用 accessToken 调用 Google Userinfo API 获取用户信息
    const response = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
      headers: {
        Authorization: `Bearer ${accessToken}`
      }
    });

    const googleUser = await response.json() as GoogleUser;

    // googleUser 数据格式示例:
    // {
    //   sub: "1234567890", // 用户在 Google 的唯一标识
    //   name: "张三",
    //   picture: "https://...",
    //   email: "zhangsan@gmail.com",
    //   email_verified: true
    // }

    // 5. 业务逻辑处理:
    // - 检查数据库中是否存在该 googleUser.sub 对应的用户
    // - 如果没有,创建新用户
    // - 在你的系统里创建会话(Session)并写入 Session Cookie
    // - 清理 google_oauth_state 和 google_code_verifier Cookie

    return new Response(null, {
      status: 302,
      headers: {
        Location: "/dashboard", // 登录成功,跳转到后台
      }
    });

  } catch (error) {
    console.error("OAuth 验证失败:", error);
    return new Response("登录验证出错", { status: 500 });
  }
}

interface GoogleUser {
  sub: string;
  name: string;
  given_name: string;
  family_name: string;
  picture: string;
  email: string;
  email_verified: boolean;
}

6. 日常使用小结与安全建议

  1. 为什么需要 PKCE (Code Verifier)? Google 登录现在强制推荐或要求使用 PKCE。arcticgoogle.createAuthorizationURLvalidateAuthorizationCode 默认就设计了传入 codeVerifier 的参数,这能有效防止授权码被拦截攻击。
  2. Cookie 的安全性: 保存 statecode_verifier 的 Cookie 必须设置为 HttpOnlySecure(生产环境),并且 SameSite 建议设置为 Lax。否则,第三方网站可能会利用你的回调接口发起跨站攻击。
  3. 过期处理: 保存 statecode_verifier 的 Cookie 有效期不宜过长,通常设置为 10 分钟(600秒)即可,超时则让用户重新发起登录。