0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutterで作る安全な権限制御【Part 3】ページとボタンを権限で制御する実装編(go_router × Riverpod)

Last updated at Posted at 2025-08-06

はじめに

前回(Part 2)では、RBACを支えるデータベース設計とER図を紹介しました。

今回はその実データを用いて、Flutterアプリ内でのページアクセス・ボタン表示の制御方法を解説します。

使用するのは:

  • ルーティング:go_router
  • 状態管理:Riverpod

前提:ログイン後に権限コード一覧を取得

まず、ログイン時に後端から取得する権限リストを前提とします:

["user:view", "user:edit", "order:view", "report:export"]

Riverpodでの権限管理Provider

final userPermissionsProvider = Provider<List<String>>((ref) {
  // 実際には SecureStorage や API から取得
  return [
    'user:view',
    'user:edit',
    'order:view',
    'report:export',
  ];
});

ページアクセス制御(go_routerのpageBuilder)

GoRoute(
  path: '/users',
  builder: (context, state) {
    final permissions = ref.watch(userPermissionsProvider);
    return permissions.contains('user:view')
      ? const UserPage()
      : const ForbiddenPage(); // アクセス拒否ページ
  },
),

このようにルート単位で制御が可能です。

ボタンの表示/非表示制御

final permissions = ref.watch(userPermissionsProvider);

if (permissions.contains('user:edit'))
  ElevatedButton(
    onPressed: () => editUser(),
    child: const Text('編集'),
  );

ユーザーが持たない権限のボタンは表示すらされません。

カスタムWidget化して再利用

class PermissionButton extends ConsumerWidget {
  final String permissionCode;
  final VoidCallback onPressed;
  final Widget child;

  const PermissionButton({
    required this.permissionCode,
    required this.onPressed,
    required this.child,
  });

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final permissions = ref.watch(userPermissionsProvider);
    if (!permissions.contains(permissionCode)) return const SizedBox.shrink();

    return ElevatedButton(onPressed: onPressed, child: child);
  }
}

使い方:

PermissionButton(
  permissionCode: 'user:delete',
  onPressed: deleteUser,
  child: const Text('削除'),
),

よくある質問

Q1. 複数の権限で許可したい場合?

final allow = permissions.any((p) => ['user:edit', 'admin'].contains(p));

Q2. 権限がないけど「ボタンは表示」だけしたい?

if (permissions.contains('user:delete')) {
  ElevatedButton(onPressed: deleteUser, child: Text("削除"));
} else {
  ElevatedButton(onPressed: null, child: Text("削除")); // 無効化
}

設計のベストプラクティス

項目 推奨実装
権限コードの一元管理 enumまたはConst管理
ページ遷移前の判定 go_routerのpageBuilderで行う
再利用性のあるWidget PermissionButtonなどで共通化

セキュリティ注意点

前回と重複しますが、改めて重要なポイント:

「ボタンを隠すこと」と「操作を防ぐこと」は別。APIは常に後端で検査すること!

シリーズリスト

Flutterで作る安全な権限制御【Part 1】業界標準と設計指針のすべて

Flutterで作る安全な権限制御【Part 2】RBACを支えるデータベース設計とER図(Mermaid付き)

Flutterで作る安全な権限制御【Part 3】ページとボタンを権限で制御する実装編(go_router × Riverpod)

Flutterで作る安全な権限制御【Part 4】Flutterアプリの反編譯リスクとセキュリティ対策
Security Flutter obfuscation

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?