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 包含
HttpOnly、SameSite=Lax,生产环境包含Secure。 requireAppAccess拒绝其他appKey的 session。requirePermission拒绝缺少权限和跨 App 权限。- logout 删除 cookie,原 session 再解析失败。
完整代码见仓库的 examples/nextjs-app。