- Next.js × 金融制約(セキュリティ・認証)対応とは?実装チェックリストとベストプラクティス
- 1. 要求整理:脅威モデルと準拠すべき枠組み
- 2. 認証・認可:OIDC/OAuth で“安全なセッション”を設計
- 2-1. OIDC(OpenID Connect:認証)+ OAuth 2.x(認可)の基本
- 2-2. Next.js(App Router)× NextAuth.js(Auth.js)の構成例
- 2-3. 認可(権限)設計の基本
- 3. フロント・サーバの防御:ヘッダー/CSP/SSRでの機密保護
- 4. API実装の安全策:入力検証・レート制限・Webhook検証
- 5. 認証以外の必須論点:アップロード・秘密管理・監査
- 6. 実装チェックリスト(持ち歩き用)
- 7. まとめ
- 参考URL
Next.js × 金融制約(セキュリティ・認証)対応とは?実装チェックリストとベストプラクティス
本記事は、Next.js(Reactベースのフルスタックフレームワーク)で金融ドメインを想定したセキュリティ・認証対応の要点を、エンジニア志望の学生にも読みやすく整理したガイドです。
金融では「PCI DSS(クレジットカード業界のセキュリティ基準)」や「FAPI(Financial-grade API:金融グレードAPI)」「SOC2」といったルールに適合する設計が求められます。ここでは難解なキーワードは括弧で短く補足しつつ、実装に直結するチェックリストを示します。
1. 要求整理:脅威モデルと準拠すべき枠組み
1-1. 想定する脅威
- 認証まわり:セッション乗っ取り、トークン漏えい、CSRF/XSS
- 通信まわり:中間者攻撃(MITM)、TLS設定不備
- データまわり:PII(個人情報)のログ流出・キャッシュ残留・過剰権限
- 運用まわり:秘密鍵・環境変数の管理不備、監査証跡の欠落
1-2. 参照すべき基準
- OWASP ASVS(Webアプリのセキュリティ検証基準)
- FAPI 2.0(OpenID Foundation:金融グレードAPIのプロファイル)
- OAuth拡張:PAR(Pushed Authorization Requests)、JAR/JARM(JWT化/署名付き応答)、DPoP(Proof of Possession)、mTLS(相互TLS)
- PCI DSS(カード情報の取扱い基準)※カード情報を扱う場合
すべてを一度に満たす必要はありません。まずは**「最小権限・短寿命・ノーキャッシュ」**を合言葉に、段階導入しましょう。
2. 認証・認可:OIDC/OAuth で“安全なセッション”を設計
2-1. OIDC(OpenID Connect:認証)+ OAuth 2.x(認可)の基本
金融では、Authorization Code + PKCE(ピクシー:認可コードの盗難対策)が事実上の標準です。IdP(アイデンティティプロバイダ:認証基盤)とはOIDCで連携し、API呼び出しには短寿命アクセストークンとローテーションするリフレッシュトークンを使います。
さらに高要件では、PAR/JAR/JARMで認可応答の改ざん耐性を高め、mTLSやDPoPでトークンの持ち主証明を強化します。
2-2. Next.js(App Router)× NextAuth.js(Auth.js)の構成例
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import OIDC from "next-auth/providers/oidc";
const handler = NextAuth({
providers: [
OIDC({
issuer: process.env.OIDC_ISSUER!, // 例: https://idp.example.com
clientId: process.env.OIDC_CLIENT_ID!,
clientSecret: process.env.OIDC_CLIENT_SECRET!,
checks: ["pkce", "state", "nonce"], // 金融ドメインでは必須
authorization: {
params: {
prompt: "login", // 明示再認証を促す
response_mode: "query" // JARM対応IdPなら 'jwt' などに
}
}
})
],
session: {
strategy: "database", // サーバー保管型セッション推奨(JWTは短寿命に)
maxAge: 60 * 15 // 15分。短寿命+再認証前提
},
cookies: {
sessionToken: {
name: "__Host-next-auth.session-token",
options: { httpOnly: true, sameSite: "strict", secure: true, path: "/" }
}
},
callbacks: {
async session({ session, user }) {
// RBAC/ABAC用の最小属性のみをセッションに載せる
(session as any).roles = (user as any)?.roles ?? [];
return session;
}
}
});
export { handler as GET, handler as POST };
注意:セッションCookieはHttpOnly + Secure + SameSite=Strict。PIIをクライアント保存しない、トークンは短寿命、リフレッシュ時はローテーション(使い回し検知)を行います。
2-3. 認可(権限)設計の基本
- 最小権限(Least Privilege):APIスコープは必要最小限に
- RBAC/ABAC:役割(Role)や属性(Attribute)でアクセスを制御
- 監査証跡:誰がいつ何をしたか(監査ログ)を必ず残す
3. フロント・サーバの防御:ヘッダー/CSP/SSRでの機密保護
3-1. セキュアヘッダー(HSTS/CSPなど)を middleware で強制
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(req: NextRequest) {
const res = NextResponse.next();
res.headers.set("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload"); // HSTS
res.headers.set("Content-Security-Policy",
"default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'");
res.headers.set("X-Content-Type-Options", "nosniff");
res.headers.set("Referrer-Policy", "no-referrer");
res.headers.set("Permissions-Policy", "geolocation=(), microphone=(), camera=()");
res.headers.set("Cross-Origin-Opener-Policy", "same-origin");
res.headers.set("Cross-Origin-Resource-Policy", "same-origin");
return res;
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"]
};
CSP(Content Security Policy)はXSS(スクリプト注入)を抑止する要。外部スクリプトを許す場合もサブリソース整合性(SRI)やTrusted Typesを検討。
3-2. PII(個人情報)のノーキャッシュとSSRの使い分け
// app/api/profile/route.ts
export const dynamic = "force-dynamic"; // ルートのキャッシュ無効化
export const revalidate = 0;
export const fetchCache = "force-no-store";
export async function GET() {
const res = new Response(JSON.stringify({ /* PIIを返すなら最小限 */ }), { status: 200 });
res.headers.set("Cache-Control", "no-store"); // 中間キャッシュも禁止
return res;
}
サーバーコンポーネントを活用し、秘密キーやDBアクセスはサーバ側で完結。ブラウザに不要なデータを送らないことが最大の防御です。
4. API実装の安全策:入力検証・レート制限・Webhook検証
4-1. 入力検証(Zod などのスキーマバリデーション)
// app/api/transfer/route.ts
import { z } from "zod";
const Body = z.object({
amount: z.number().positive().max(1_000_000),
currency: z.enum(["JPY", "USD"]),
memo: z.string().max(140).optional(),
});
export async function POST(req: Request) {
const json = await req.json().catch(() => null);
const parsed = Body.safeParse(json);
if (!parsed.success) return new Response("Bad Request", { status: 400 });
// ...安全な処理(認可チェック・台帳書き込み等)
return new Response("OK", { status: 200, headers: { "Cache-Control": "no-store" } });
}
サーバ側で必ず検証。フロントのバリデーションはUX用であり、セキュリティにはなりません。
4-2. レート制限(Rate Limiting)
// 例: Upstash Redis を使った固定窓制限のイメージ
import { NextRequest, NextResponse } from "next/server";
import { Redis } from "@upstash/redis";
const redis = Redis.fromEnv();
const WINDOW = 60; // 秒
const LIMIT = 10; // リクエスト数
export async function middleware(req: NextRequest) {
const key = `rl:${req.ip}:${new URL(req.url).pathname}:${Math.floor(Date.now()/1000/WINDOW)}`;
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, WINDOW);
if (count > LIMIT) return new NextResponse("Too Many Requests", { status: 429 });
return NextResponse.next();
}
金融ではメモリ内だけの制限は不可(スケールアウトで抜ける)。分散ストア(Redis等)で強制しましょう。
4-3. Webhook署名検証
import { subtle } from "crypto".webcrypto;
async function verifyHmac(signatureHex: string, payload: string, secret: string) {
const key = await subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const sig = await subtle.sign("HMAC", key, new TextEncoder().encode(payload));
const expectedHex = Buffer.from(sig).toString("hex");
// 時間差攻撃に強い比較
return expectedHex.length === signatureHex.length &&
crypto.timingSafeEqual(Buffer.from(expectedHex, "hex"), Buffer.from(signatureHex, "hex"));
}
Webhookは必ず署名を検証し、リプレイ対策にタイムスタンプや一度きりのnonceを導入します。
5. 認証以外の必須論点:アップロード・秘密管理・監査
5-1. ファイルアップロードの安全設計
- クライアント → 署名付きURL(例:S3 Presigned URL)で直接アップロード
- MIME/拡張子のホワイトリスト、サイズ制限、ウイルススキャン(例:ClamAV)
- 保存時はサーバー側暗号化(KMSキー管理)+ アクセスログ必須
5-2. 秘密情報(シークレット)の管理
- env管理:ローカルは .env、クラウドはKMS/Vault等。リポジトリに入れない
- 鍵ローテーション:短い有効期限、漏えい検知で即失効
- 権限分離:CIから読める範囲を最小に
5-3. 監査ログとアラート
- 重要操作(認証・送金・口座更新)に一意の追跡IDを付与
- PIIはログに書かない(トークン化/マスキング)
- SIEM(セキュリティ監視基盤)へ集約、閾値でアラート
6. 実装チェックリスト(持ち歩き用)
- 認証:OIDC + PKCE、短寿命セッション、CookieはHttpOnly/Secure/SameSite=Strict
- OAuth拡張:可能なら PAR/JAR/JARM、DPoP or mTLS を検討
- フロント:CSP/HSTS/NoSniff 等ヘッダー、SRI/Trusted Types、XSS対策
- API:Zod等でサーバ側バリデーション、レート制限は分散ストア
- データ:PIIは最小化、no-store、ブラウザ/中間キャッシュ禁止
- アップロード:署名URL、拡張子/MIME制限、AVスキャン
- 秘密:envはVault/KMS、ローテーション、最小権限
- 監査:操作ログ・監査ログ・アラートを整備
7. まとめ
金融ドメインのNext.jsは、「最小権限」「短寿命」「ノーキャッシュ」の三本柱に、OIDC/OAuthの堅牢なフローとCSP等の標準防御を積み上げるのが基本戦略です。難しい略語(FAPI/PAR/JAR/DPoP…)は少しずつでOK。まずは安全なセッションとヘッダー、入力検証とレート制限、秘密管理から着手し、準拠要件に合わせて段階的に強化しましょう。