IWish Auth开发者文档
V1 Alpha GitHub

Next.js

使用 App Router 和 TypeScript SDK 接入

Next.js App Router 使用 @iwish/auth-sdk/next。Adapter 负责 state、PKCE、短时 cookie、授权码交换、App session cookie 和稳定错误类型。

1. 创建服务端 Auth 实例

// app/lib/auth.ts
import { createIWishAuth } from "@iwish/auth-sdk/next";

export function getAuth() {
  return createIWishAuth({
    apiUrl: process.env.IWISH_AUTH_API_URL!,
    portalUrl: process.env.IWISH_AUTH_PORTAL_URL!,
    appKey: process.env.IWISH_AUTH_APP_KEY!,
    clientId: process.env.IWISH_AUTH_CLIENT_ID!,
    clientSecret: process.env.IWISH_AUTH_CLIENT_SECRET!,
    redirectUri: process.env.IWISH_AUTH_REDIRECT_URI!,
    secureCookies: process.env.NODE_ENV === "production"
  });
}

该模块只能被 Server Component、Route Handler 或 Server Action 导入。不要添加 "use client"

2. 登录 Route Handler

// app/auth/login/route.ts
import { getAuth } from "../../lib/auth";

export async function GET(request: Request) {
  const returnTo = new URL(request.url).searchParams.get("returnTo") ?? "/";
  return getAuth().beginLogin(request, { returnTo });
}

returnTo 只允许当前 App 同源路径。SDK 会把外部 URL 归一化为 /

3. Callback Route Handler

// app/auth/callback/route.ts
import { getAuth } from "../../lib/auth";

export async function GET(request: Request) {
  return getAuth().handleCallback(request);
}

Callback 会校验 state,使用 PKCE verifier 和 confidential client secret 交换一次性 code,并设置 HttpOnly App session cookie。

4. 登出

// app/auth/logout/route.ts
import { getAuth } from "../../lib/auth";

export async function POST(request: Request) {
  return getAuth().logout(request, { redirectTo: "/" });
}

SDK 会尽力撤销服务端 session;即使 Auth API 暂时不可达,也会删除本 App cookie。

5. 读取 Session

// app/api/session/route.ts
import { IWishAuthError } from "@iwish/auth-sdk";
import { getAuth } from "../../lib/auth";

export async function GET(request: Request) {
  try {
    return Response.json(await getAuth().requireAppAccess(request));
  } catch (error) {
    if (error instanceof IWishAuthError) {
      return Response.json({ error: error.code }, { status: error.status });
    }
    throw error;
  }
}

不要只调用 getSession() 后信任本地 cookie。requireAppAccess() 会通过 Auth API 解析 opaque session 并确认该 session 属于当前 App。

6. 权限保护

export async function GET(request: Request) {
  const session = await getAuth().requirePermission(request, "reporting.report.read");
  const visibleProjectIds = session.clientAssignments.map((item) => item.clientId);
  return Response.json(await loadReports({ userId: session.user.id, visibleProjectIds }));
}

requirePermission 同时验证 session、App 和权限命名空间。跨 App 权限 key 即使意外出现在上下文中也会被拒绝。

7. Server Component 边界

Route Handler 接收标准 Request,可以直接使用 Adapter。Server Component 没有原始 Request 时,建议通过受保护的内部 Route Handler 获取 session,或使用 Next.js headers() 构造只读 Request;不要把 opaque session token传给 Client Component。

8. 测试清单

  • beginLogin 返回 302,并设置 state/verifier cookie。
  • callback state 不匹配返回 401。
  • callback 后 cookie 包含 HttpOnlySameSite=Lax,生产环境包含 Secure
  • requireAppAccess 拒绝其他 appKey 的 session。
  • requirePermission 拒绝缺少权限和跨 App 权限。
  • logout 删除 cookie,原 session 再解析失败。

完整代码见仓库的 examples/nextjs-app