

【設計】ECS Fargate + Next.js + FastAPI のアクセス制御設計 — SSR内部通信の認証をどう解決するか
はじめに
AWS ECS Fargate上でNext.js(フロントエンド)とFastAPI(バックエンド)を動かし、ALB + Cognito + Entra IDでOIDC認証を行っている環境に、「許可されたメールアドレスのみアクセス可能」というアクセス制御を導入することになった。
一見シンプルな要件だが、SSR(Server Side Rendering)の内部通信がALBを経由しないため、認証ヘッダーが届かないという根本的な問題に直面した。
この記事でわかること
-
ALBが付与する
x-amzn-oidc-dataヘッダーの特性と制約 -
SSR内部通信で認証情報が失われる理由
-
Next.js middleware / Server Component / CSR での認可チェックの比較
-
x-internal-api-keyパターンによるSSR内部通信の認証設計
対象読者
-
AWS ECS + ALB OIDC環境でNext.jsアプリを運用している方
-
SSRとCSRで認証の扱いが異なることに困っている方
-
アプリ層でのアクセス制御設計を検討している方
前提条件
-
AWS ECS Fargate + ALB(Cognito OIDC認証)
-
Next.js 16(App Router, standalone mode, basePath:
/sm) -
FastAPI(Python)
-
同一VPC内でCloud Map経由のサービス間通信
通信経路の整理
まず、リクエストがどの経路を通るかを理解する必要がある。
ブラウザ → ALB → バックエンド(CSR)
ブラウザからのAPI呼び出しはALBを経由する。ALBはCognito認証後に x-amzn-oidc-data ヘッダー(JWT)を自動付与するため、バックエンドはこのJWTからメールアドレスを取得できる。
Next.js SSR → バックエンド(内部通信)
Server Componentからのデータ取得はECS内部のCloud Map DNS経由で直接バックエンドに接続する。ALBを経由しないため、x-amzn-oidc-data ヘッダーは存在しない。
# CSR: ALB経由 → ヘッダーあり
ブラウザ → ALB(x-amzn-oidc-data付与) → FastAPI
# SSR: 内部通信 → ヘッダーなし
Next.js SSR → Cloud Map → FastAPI
この「SSR内部通信にはOIDCヘッダーがない」という事実が、設計の核心的な課題となる。
検討した3つのパターン
パターンA: Next.js middleware で認可チェック
middleware.tsで全リクエストをインターセプトし、x-amzn-oidc-data からメールを取得。バックエンドの /auth/check に内部通信で問い合わせる方式。
問題点:
-
middlewareはEdge Runtimeで動作し、
api-server.ts(Node.js Runtime)を直接importできない -
内部通信用のfetchを別途実装する必要があり、ヘッダー転送やエラー処理が複雑化
-
basePath(
/sm)の二重化問題が発生しやすい -
実際にstg環境で複数の問題が発生しデプロイ不可に
パターンB: CSR コンポーネントで認可チェック
layout.tsxに<Authenticator>クライアントコンポーネントを配置。useEffectでマウント後にブラウザからALB経由で/auth/checkを呼ぶ方式。
メリット: 実装が最もシンプルで、SSR内部通信の問題が完全に無関係。
デメリット: SSRのデータフェッチが認可チェック前に実行され、ローディング表示が一瞬入る。
パターンC: Server Component(layout.tsx)で認可チェック【採用】
ルートのlayout.tsxをasync関数にし、headers()でx-amzn-oidc-dataを取得してメールを抽出。api-server.ts経由でバックエンドに許可チェックする方式。
メリット: Node.js Runtimeで動作し、api-server.tsを直接importできる。Edge Runtime制約なし、basePath問題なし。未許可ユーザーにはページのレンダリング自体が実行されない。
比較表
| 評価軸 | A: middleware | B: CSR | C: Server Component |
|---|---|---|---|
| セキュリティ | 高 | 中 | 高 |
| UX | コンテンツ見えない | ローディング挟む | コンテンツ見えない |
| 実装の複雑さ | 高 | 低 | 低〜中 |
| SSR内部通信 | fetchを別途実装 | 無関係 | api-server.ts流用 |
| Edge Runtime制約 | あり | なし | なし |
採用した設計: x-internal-api-key パターン
パターンCを採用し、SSR内部通信には共有秘密鍵(x-internal-api-key)で「信頼された内部通信」と識別する設計とした。
バックエンドの認証ミドルウェアは、リクエストを上から順に判定する:
-
x-internal-api-keyがある → 信頼された内部通信、認証スキップ -
x-amzn-oidc-dataがある → JWT検証 → メール抽出 → 許可リストチェック -
どちらもない → 401
フロントエンドでは、layout.tsxでheaders()からOIDCヘッダーを取得し、api-server.ts経由でバックエンドに認可チェックを行う。
// (authenticated)/layout.tsx
export default async function AuthenticatedLayout({ children }) {
const { authorized, is_admin } = await checkAuth();
if (!authorized) redirect("/unauthorized");
return (
<AuthProvider isAdmin={is_admin ?? false}>
<Header />
<main>{children}</main>
</AuthProvider>
);
}
Edge RuntimeとNode.js Runtimeの違い
Next.jsには2つのサーバーサイド実行環境がある:
-
Edge Runtime: 軽量だが制約あり。
fs、net等のNode.js APIが使えない。middlewareはデフォルトでこちら -
Node.js Runtime: フル機能。Server Components、API Routesはこちら
middlewareからはapi-server.tsを直接importできないが、layout.tsx(Server Component)からは同じNode.js Runtimeなのでそのまま使える。
Tips
-
ALBの
x-amzn-oidc-dataはALB→ターゲット間でのみ付与される。SSR内部通信では使えない -
x-internal-api-keyはECS内部通信でのみ使用し、セキュリティグループで外部からのアクセスを遮断する前提 -
Server ComponentのルートグループでCSR/SSRの認証パスを分離すると見通しがよい
まとめ
-
SSR内部通信ではALBのOIDCヘッダーが届かないため、別の認証手段が必要
-
Next.js middlewareはEdge Runtime制約があり、Server Componentでの認可チェックが最もシンプル
-
x-internal-api-keyパターンでSSR内部通信を信頼済みとして扱う設計が有効 -
layout.tsxでの認可チェックにより、未許可ユーザーにはレンダリング自体が実行されないセキュリティを実現


