关于 encore-auth
Encore Authentication 是一项用于在 Encore.ts API 中实现身份验证的技能,涵盖认证处理器、端点保护、访问认证数据、服务间传递及测试方法。
主要功能
- 01设置认证处理器
- 02控制端点身份验证
- 03访问请求认证数据
- 04在服务调用间传递身份
- 05JWT、API Key 和 Cookie 模式
- 06使用 Vitest 测试身份验证
适用场景
- 01为 API 端点要求身份验证
- 02在内部服务调用中传递身份
- 03验证 JWT、API Key 或 Cookie
- 04测试受保护端点的行为
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ encoredev/ skills --skill encore-auth安装后在 Claude Code 中输入/encore-auth确认已加载。
详细介绍
Encore 身份验证
说明
Encore.ts 提供内置身份验证系统,用于识别 API 调用方并保护端点。
1. 创建身份验证处理程序
// auth.ts
import { Header, Gateway } from "encore.dev/api";
import { authHandler } from "encore.dev/auth";
// Define what the auth handler receives
interface AuthParams {
authorization: Header<"Authorization">;
}
// Define what authenticated requests will have access to
interface AuthData {
userID: string;
email: string;
role: "admin" | "user";
}
export const auth = authHandler<AuthParams, AuthData>(
async (params) => {
// Validate the token (example with JWT)
const token = params.authorization.replace("Bearer ", "");
const payload = await verifyToken(token);
if (!payload) {
throw APIError.unauthenticated("invalid token");
}
return {
userID: payload.sub,
email: payload.email,
role: payload.role,
};
}
);
// Register the auth handler with a Gateway
export const gateway = new Gateway({
authHandler: auth,
});
2. 保护端点
import { api } from "encore.dev/api";
// Protected endpoint - requires authentication
export const getProfile = api(
{ method: "GET", path: "/profile", expose: true, auth: true },
async (): Promise<Profile> => {
// Only authenticated users reach here
}
);
// Public endpoint - no authentication required
export const healthCheck = api(
{ method: "GET", path: "/health", expose: true },
async () => ({ status: "ok" })
);
3. 在端点中访问身份验证数据
import { api } from "encore.dev/api";
import { getAuthData } from "~encore/auth";
export const getProfile = api(
{ method: "GET", path: "/profile", expose: true, auth: true },
async (): Promise<Profile> => {
const auth = getAuthData()!; // Non-null when auth: true
return {
userID: auth.userID,
email: auth.email,
role: auth.role,
};
}
);
身份验证处理程序的行为
| 场景 | 处理程序返回值 | 结果 |
|---|---|---|
| 凭据有效 | AuthData 对象 | 请求通过身份验证 |
| 凭据无效 | 抛出 APIError.unauthenticated() | 按未进行身份验证处理 |
| 其他错误 | 抛出其他错误 | 请求中止 |
端点的身份验证
| 端点配置 | 请求是否携带身份验证信息 | 结果 |
|---|---|---|
auth: true | 是 | 继续处理,并提供身份验证数据 |
auth: true | 否 | 401 未进行身份验证 |
auth: false 或省略 | 是 | 继续处理(可使用身份验证数据) |
auth: false 或省略 | 否 | 继续处理(无身份验证数据) |
服务间身份验证传播
身份验证数据会自动传播到内部服务调用:
import { user } from "~encore/clients";
import { getAuthData } from "~encore/auth";
export const getOrderWithUser = api(
{ method: "GET", path: "/orders/:id", expose: true, auth: true },
async ({ id }): Promise<OrderWithUser> => {
const auth = getAuthData()!;
// Auth is automatically propagated to this call
const orderUser = await user.getProfile();
return { order: await getOrder(id), user: orderUser };
}
);
覆盖身份验证数据
进行服务间调用时,你可以显式覆盖身份验证数据:
import { user } from "~encore/clients";
// Override auth data for this specific call
const adminUser = await user.getProfile(
{},
{ authData: { userID: "admin-123", email: "[email protected]", role: "admin" } }
);
常见身份验证模式
JWT 令牌验证
import { jwtVerify } from "jose";
import { secret } from "encore.dev/config";
const jwtSecret = secret("JWTSecret");
async function verifyToken(token: string): Promise<JWTPayload | null> {
try {
const { payload } = await jwtVerify(
token,
new TextEncoder().encode(jwtSecret())
);
return payload;
} catch {
return null;
}
}
API 密钥身份验证
export const auth = authHandler<AuthParams, AuthData>(
async (params) => {
const apiKey = params.authorization;
const user = await db.queryRow<User>`
SELECT id, email, role FROM users WHERE api_key = ${apiKey}
`;
if (!user) {
throw APIError.unauthenticated("invalid API key");
}
return {
userID: user.id,
email: user.email,
role: user.role,
};
}
);
基于 Cookie 的身份验证
interface AuthParams {
cookie: Header<"Cookie">;
}
export const auth = authHandler<AuthParams, AuthData>(
async (params) => {
const sessionId = parseCookie(params.cookie, "session");
if (!sessionId) {
throw APIError.unauthenticated("no session");
}
const session = await getSession(sessionId);
if (!session || session.expiresAt < new Date()) {
throw APIError.unauthenticated("session expired");
}
return {
userID: session.userID,
email: session.email,
role: session.role,
};
}
);
使用身份验证进行测试
使用 Vitest 在测试中模拟身份验证:
import { describe, it, expect, vi } from "vitest";
import * as auth from "~encore/auth";
import { getProfile } from "./api";
describe("authenticated endpoints", () => {
it("returns profile for authenticated user", async () => {
// Mock getAuthData to return test user
const spy = vi.spyOn(auth, "getAuthData");
spy.mockImplementation(() => ({
userID: "test-user-123",
email: "[email protected]",
role: "user",
}));
const profile = await getProfile();
expect(profile.email).toBe("[email protected]");
spy.mockRestore();
});
});
此模拟不会影响通过 ~encore/clients 发起的调用。要在测试中调用另一个服务上的 auth: true 端点,请将 { authData: ... } 作为第二个参数传入(参见上文“覆盖身份验证数据”)。
指南
- 身份验证处理程序必须注册到 Gateway
- 使用
~encore/auth中的getAuthData()访问身份验证数据 - 未进行身份验证的请求中,
getAuthData()会返回null - 身份验证数据会在服务间调用中自动传播
- 凭据无效时,抛出
APIError.unauthenticated() - 保持身份验证处理程序快速运行——它会在每个经过身份验证的请求中执行
评论
支持 Markdown