メインコンテンツまでスキップ

ロール

ロールはZeltの認可システムの基盤です。ユーザーが何をできるかを定義します。

ロールとは

ロールは権限レベルや能力を表すシンプルな文字列です:

['admin', 'editor', 'viewer']
['owner', 'member', 'guest']
['read:users', 'write:users', 'delete:users']

ロールは認証時にsetUser()で割り当てられます:

setUser(
{ id: user.id, name: user.name },
['admin', 'user'] // ← ロール
);

ロール型の定義

RequestContextSchemaを使用してロールを型付け:

declare module '@zeltjs/core' {
interface RequestContextSchema {
user: { id: string; name: string };
authRoles: ('admin' | 'editor' | 'viewer')[];
}
}

これにより以下が提供されます:

  • setUser()呼び出し時の自動補完
  • @Authorized(['...'])での型チェック
  • 型安全なcurrentRoles()の戻り値

ロール設計パターン

階層型ロール

他のロールを暗示するロールを定義:

type Role = 'admin' | 'editor' | 'viewer';

const roleHierarchy: Record<Role, Role[]> = {
admin: ['admin', 'editor', 'viewer'],
editor: ['editor', 'viewer'],
viewer: ['viewer'],
};

// ユーザー設定時にロールを展開
setUser(user, roleHierarchy[user.primaryRole]);

リソーススコープ付きロール

ロール名にリソースコンテキストを含める:

type Role =
| 'admin'
| `project:${string}:owner`
| `project:${string}:member`
| `team:${string}:admin`;

// ユーザーはproject-123のオーナー、team-456の管理者
setUser(user, ['project:123:owner', 'team:456:admin']);

権限ベースロール

きめ細かい権限文字列を使用:

type Permission =
| 'read:users'
| 'write:users'
| 'delete:users'
| 'read:posts'
| 'write:posts';

// ロールは権限にマップ
const rolePermissions: Record<string, Permission[]> = {
admin: ['read:users', 'write:users', 'delete:users', 'read:posts', 'write:posts'],
editor: ['read:users', 'read:posts', 'write:posts'],
viewer: ['read:users', 'read:posts'],
};

ロールの取得元

データベース

ユーザーレコードとともにロールを保存:

// Userテーブル
interface User {
id: string;
name: string;
roles: string[]; // ['admin', 'user']
}

// 認証ミドルウェアで
const user = await db.users.findById(payload.sub);
setUser(
{ id: user.id, name: user.name },
user.roles
);

JWTクレーム

JWTペイロードにロールを含める:

// 署名時
const token = await jwtService.sign({
sub: user.id,
roles: user.roles,
});

// 検証時(JwtConfig.resolveUserで)
override get resolveUser() {
return async (payload: JwtPayload) => ({
user: { id: payload.sub },
roles: payload.roles as string[],
});
}

セッションデータ

セッションにロールを保存:

// ログイン時
setSession({ userId: user.id, roles: user.roles });

// 認証ミドルウェアで
const session = getSession();
if (session) {
const user = await db.users.findById(session.userId);
setUser(user, session.roles);
}

外部サービス

IDプロバイダーからロールを取得:

const userInfo = await identityProvider.getUserInfo(token);
const roles = await identityProvider.getRoles(userInfo.sub);
setUser(
{ id: userInfo.sub, name: userInfo.name },
roles
);

ロール割り当て戦略

静的割り当て

ロールは一度設定され、めったに変更されない:

// 管理者がAPIでロールを割り当て
@Authorized(['admin'])
@Post('/users/:id/roles')
async assignRoles(id = pathParam('id'), body = bodyParam(RolesSchema)) {
await db.users.update(id, { roles: body.roles });
return { success: true };
}

動的割り当て

ロールはコンテキストに基づいて計算される:

// ロールはリソースの所有権に依存
const project = await db.projects.findById(projectId);
const roles = [];

if (project.ownerId === user.id) {
roles.push('project:owner');
}
if (project.memberIds.includes(user.id)) {
roles.push('project:member');
}

setUser(user, roles);

時間ベースロール

ロールは時間に基づいて期限切れまたは有効化:

const roles = user.roles.filter(role => {
const grant = user.roleGrants.find(g => g.role === role);
if (!grant) return true;

const now = Date.now();
if (grant.startsAt && now < grant.startsAt) return false;
if (grant.expiresAt && now > grant.expiresAt) return false;
return true;
});

setUser(user, roles);

ロールへのアクセス

ハンドラー内で

import { currentRoles } from '@zeltjs/core';

@Get('/dashboard')
dashboard() {
const roles = currentRoles();

return {
canManageUsers: roles.includes('admin'),
canEditContent: roles.includes('editor') || roles.includes('admin'),
};
}

サービス内で

class PostService {
canDelete(post: Post): boolean {
const roles = currentRoles();
const user = currentUser();

if (roles.includes('admin')) return true;
if (post.authorId === user?.id) return true;
return false;
}
}

ベストプラクティス

ロールはシンプルに

フラットな文字列を使用し、ネストしたオブジェクトは避ける:

// ✅ 良い
['admin', 'editor', 'viewer']

// ❌ 避ける
[{ name: 'admin', level: 10, permissions: [...] }]

ロールは粗いアクセスに使用

ロールは「このユーザーはこの機能エリアにアクセスできるか?」を答える、「このユーザーはこの特定のレコードを編集できるか?」ではない:

// ✅ ロールベース: 「管理セクションにアクセスできる」
@Authorized(['admin'])
@Get('/admin/dashboard')

// ❌ ロールではない: 「投稿#123を編集できる」
// → 代わりにサービスロジックで処理

ロール爆発を避ける

すべてのアクションに対してロールを作成しない:

// ❌ ロールが多すぎる
['can_view_users', 'can_create_users', 'can_edit_users', 'can_delete_users', ...]

// ✅ 意味のあるロールにグループ化
['admin', 'user_manager', 'viewer']

ロールをドキュメント化

中央リファレンスを維持:

/**
* アプリケーションロール
*
* - admin: システム全体へのフルアクセス
* - editor: コンテンツの作成・編集が可能
* - viewer: 読み取り専用アクセス
* - moderator: ユーザー生成コンテンツの管理が可能
*/
type Role = 'admin' | 'editor' | 'viewer' | 'moderator';