はじめに
前回
Web アプリケーションが複雑化するにつれ アクセス制御 はバグの温床になりやすい。本稿では TypeScript の型システムと条件付き型を活用し、実装段階で不正アクセスをコンパイルエラーにする 設計手法を解説する。
1. 権限モデルを分解する
概念 | 例 | 備考 |
---|---|---|
Role |
"admin" , "editor" , "viewer"
|
ユーザー属性 |
Resource |
"article" , "comment"
|
対象リソース |
Action |
"create" , "read" , "update" , "delete"
|
操作 |
Role × Resource × Action の直積がアクセス制御表となる。
2. 権限マトリクスを型で宣言
const ACL = {
admin: {
article: ["create", "read", "update", "delete"],
comment: ["create", "read", "update", "delete"],
},
editor: {
article: ["create", "read", "update"],
comment: ["read", "update"],
},
viewer: {
article: ["read"],
comment: ["read"],
},
} as const;
-
as const
でリテラル型を保持し、配列要素が Union 型に展開される。
type Role = keyof typeof ACL; // "admin" | "editor" | "viewer"
type Resource<R extends Role> = keyof typeof ACL[R];
3. 型安全な canAccess
関数
type CanAccess<
R extends Role,
Res extends Resource<R>,
Act extends (typeof ACL)[R][Res][number],
> = true;
// 使用例
const ok: CanAccess<"editor", "article", "update"> = true; // ✅
// const ng: CanAccess<"viewer", "article", "update"> = true; // ❌ 型エラー
- ジェネリクスと indexed access type で 許可された組み合わせのみ 型が解決する。
実装版
function canAccess<R extends Role, Res extends Resource<R>>(
role: R,
resource: Res,
action: (typeof ACL)[R][Res][number]
): boolean {
return ACL[role][resource].includes(action as any);
}
コンパイル時は型保証、ランタイムは配列検索で軽量。
4. API ルートガードに適用
// Next.js Middleware 例
import type { NextRequest } from "next/server";
export function middleware(req: NextRequest) {
const role = req.headers.get("x-role") as Role;
const url = new URL(req.url);
const [_, res, id] = url.pathname.split("/");
const action = req.method === "GET" ? "read" : "update";
if (!canAccess(role, res as any, action as any)) {
return new Response("Forbidden", { status: 403 });
}
}
IDE 補完でリソースとアクションが選択肢として出るため、ミススペルや未定義操作を事前排除 できる。
5. 動的 Role 追加を考慮した拡張
type Merge<A, B> = {
[K in keyof A | keyof B]: K extends keyof B
? B[K]
: K extends keyof A
? A[K]
: never;
};
type ExtendedACL = Merge<typeof ACL, {
guest: {
article: ["read"];
comment: [];
};
}>;
- 型ユーティリティでマトリクスをマージし、既存ロジックを崩さず拡張。
6. 落とし穴と対策
落とし穴 | 原因 | 対策 |
---|---|---|
配列要素がリテラル型化されない |
as const を忘れる |
すべての ACL オブジェクトに as const を付与 |
ACL が肥大化し可読性低下 | 大規模 Role 数 | Domain ごとに ACL を分割し Merge で合成 |
ランタイム追加 Role | 静的型に含まれず any 化 |
DB から ACL を読み込み、as const 生成を自動スクリプト化 |
まとめ
- Role × Resource × Action をリテラルで定義し、Union 型へ展開。
-
indexed access
とジェネリクスで 許可組み合わせのみ 関数を受け付ける。 - ランタイムでも軽量チェックを行い、静的・動的の二段構えで安全を確保する。
次回は キャッシュ戦略と型保証 をテーマに、React Query と Suspense を用いたデータ整合性モデルを深掘りする。