🕌

# 5.5 Pinia(状態管理)と認証ストア拡張

に公開

前回は Vue から Django REST Framework の API を呼び出し、一覧データを表示するところまで確認した。
今回は次のステップとして、認証情報を Pinia ストアに保持する仕組み を導入する。
さらに /auth/status/ API を拡張し、ユーザーの 部署(dept)・ロール(role) も返すようにして、メニュー制御につなげていく。


1. /auth/status/ API の設計

Django 側でログイン確認用の API を実装する。
最低限返すのは以下の情報:

  • username(ログインID)
  • full_name(表示名)
  • is_staff(管理者フラグ)
  • departments(所属部署リスト)
  • roles(付与ロールリスト)

レスポンス例:

{
  "username": "taro",
  "full_name": "山田 太郎",
  "is_staff": true,
  "departments": [
    { "code": "A001", "name": "営業一課" },
    { "code": "A002", "name": "営業二課" }
  ],
  "roles": ["order_approve", "asset_manager"]
}

📌 解説

  • 単にユーザー名だけでなく、部署とロールを同時に返すのがポイント。
  • フロント側ではこの情報を基に「誰にどのメニューを見せるか」を制御できる。

部署情報と兼務の扱い

実務では「1人1部署」ではなく、複数部署を兼務するユーザー が存在する。

"departments": [
  { "code": "A001", "name": "営業一課" },
  { "code": "A002", "name": "営業二課" }
]
  • 主所属:営業一課
  • 兼務所属:営業二課

📌 解説

  • 兼務情報を返しておくと、Vue 側で「どの部署で操作中か」を切り替える UI を作れる。
  • 単一所属のユーザーはセレクト不要、複数部署の場合のみ選択 UI を出せばよい。

Vue 側での対応例:

<v-select
  v-if="auth.departments.length > 1"
  :items="auth.departments"
  item-title="name"
  item-value="code"
  v-model="currentDept"
  label="操作中の部署を選択"
/>

📌 解説

  • 部署が1つなら選択 UI は非表示、複数ならセレクトを出す。
  • item-title="name" / item-value="code" によって、Vue 側で表示名とコードを分けて管理できる。

2. Pinia ストアの拡張

src/store/auth.js を以下のように修正する。

import { defineStore } from "pinia";
import apiClient from "@/plugins/axios";

export const useAuthStore = defineStore("auth", {
  state: () => ({
    user: null,
    departments: [],
    roles: [],
    loading: false,
    error: "",
  }),

  actions: {
    async fetchUser() {
      this.loading = true;
      this.error = "";
      try {
        const res = await apiClient.get("/auth/status/");
        this.user = {
          username: res.data.username,
          full_name: res.data.full_name,
          is_staff: res.data.is_staff,
        };
        this.departments = res.data.departments || [];
        this.roles = res.data.roles || [];
      } catch {
        this.error = "ログイン情報を取得できませんでした";
      } finally {
        this.loading = false;
      }
    },

    logout() {
      this.user = null;
      this.departments = [];
      this.roles = [];
    },
  },

  getters: {
    isLoggedIn: (state) => !!state.user,
    username: (state) => state.user?.full_name || "",
    hasRole: (state) => (role) => state.roles.includes(role),
    belongsTo: (state) => (deptCode) =>
      state.departments.some((d) => d.code === deptCode),
  },
});

📌 解説

  • stateuser / departments / roles を持たせる。

  • actions

    • fetchUser()/auth/status/ を叩いてログイン情報を取得。
    • logout() … state をクリア。
  • getters

    • isLoggedIn … ログイン状態判定。
    • username … フルネームを返す。
    • hasRole(role) … 特定のロールを持っているか。
    • belongsTo(deptCode) … 特定の部署に所属しているか。

👉 こうすることで、Vue 側では 簡単に条件判定 ができるようになる。


3. Vue 側での利用例

<template>
  <div>
    <p v-if="auth.isLoggedIn">
      {{ auth.username }} さん(所属: {{ auth.departments[0]?.name }})
    </p>

    <v-btn v-if="auth.hasRole('order_approve')" color="primary">
      受注承認
    </v-btn>
  </div>
</template>

<script setup>
import { useAuthStore } from "@/store/auth";

const auth = useAuthStore();
auth.fetchUser();
</script>

📌 解説

  • auth.isLoggedIn でログイン済みか確認。
  • auth.username で表示名を取得。
  • auth.hasRole('order_approve') でロール判定し、承認ボタンを表示。
  • auth.fetchUser() を起動時に実行してユーザー情報を反映。

4. 実務的なポイント

  • 部署とロールはまとめて返す
    → Vue 側で「部署 A かつ role B」のような複雑条件を簡単に制御できる。

  • getter で判定を共通化
    → 各画面で毎回 .includes("role") と書くのは非効率。

  • キャッシュ化
    /auth/status/ はアプリ起動時に1回叩くのが基本。
    → 頻繁に呼ばず、Pinia に保持して利用する。


💡 AI 活用のポイント

  • App.vue での初期化前提を忘れられる
    実務では App.vue 起動時に /auth/status/ を必ず叩いて、Pinia にユーザー情報を保持しておくのが基本。
    しかし AI はこの前提をよく忘れ、各コンポーネントで auth() を直接呼ぶコードを生成することがある。
    👉 「認証情報は App.vue で初期化済み」「Pinia に保存されているものを使う」と強調して指示することが必要なケースがある。

  • 既存のストアを無視して新規に書き出すことがある
    auth ストアがあるにも関わらず、AI が再度 useAuthStore() を定義し直したり、ローカル state を作ってしまうことがある。
    👉 「既存の auth ストアを必ず利用して」と条件をつけると回避しやすい。

  • レスポンス構造が勝手に解釈される
    /auth/status/departments: [], roles: [] の配列を返す前提なのに、AI が 単一オブジェクトや文字列を扱うコードを生成してしまうことがある。
    👉 「departments と roles は必ず配列で返る」と念押ししておかないと、フロントコードが食い違う。

  • たちが悪いのは “部分的に正しいコード” を混ぜてくること
    一見正しそうな実装でも、よく見ると「ストアを再定義している」「配列を配列として扱っていない」などの地雷が混ざる。
    👉 生成コードをそのままコピペせず、必ず既存の前提と突き合わせて確認することが必須。


まとめ

  • /auth/status/ API を拡張し、部署・ロール情報も返すようにした
  • Pinia ストアを使い、認証情報を全画面で共有可能にした
  • Vue 側からは auth.hasRole()auth.belongsTo() を呼ぶだけで判定可能
  • これにより メニュー制御や画面制御の基盤 が整った

👉 次回はこの仕組みを利用して、部署・ロールごとのメニュー制御 を実装していく。

Discussion