mcpField
E
encore-auth

encore-auth

github.com/encoredev安全30835 次安装2 天前更新
代码仓库

关于 encore-auth

Encore Authentication 是一项用于在 Encore.ts API 中实现身份验证的技能,涵盖认证处理器、端点保护、访问认证数据、服务间传递及测试方法。

主要功能

  1. 01设置认证处理器
  2. 02控制端点身份验证
  3. 03访问请求认证数据
  4. 04在服务调用间传递身份
  5. 05JWT、API Key 和 Cookie 模式
  6. 06使用 Vitest 测试身份验证

适用场景

  1. 01为 API 端点要求身份验证
  2. 02在内部服务调用中传递身份
  3. 03验证 JWT、API Key 或 Cookie
  4. 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