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キー、Cookieのパターン
  6. 06Vitestによる認証テスト

活用シーン

  1. 01APIエンドポイントへの認証要求
  2. 02内部サービス呼び出しでのID伝播
  3. 03JWT、APIキー、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 Unauthenticated
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 エンドポイントを呼び出すには、2 番目の引数として { authData: ... } を渡します(上記の「認証データのオーバーライド」を参照)。

ガイドライン

  • 認証ハンドラーは Gateway に登録する必要があります
  • 認証データにアクセスするには ~encore/auth の getAuthData() を使用します
  • getAuthData() は未認証のリクエストでは null を返します
  • 認証データはサービス間呼び出しで自動的に伝播します
  • 無効な認証情報には APIError.unauthenticated() をスローします
  • 認証ハンドラーは高速に保ちます - 認証されたすべてのリクエストで実行されます

コメント