今日はVue3のフレームワークを使ってGCP(Google Cloud Platform)のAuthenticationの機能からGoogleでログインしているユーザーを認知し、ログインしているユーザーのみにFirestoreのデータベースのCRUD権限を与える方法を紹介します。
まず初めにこの記事を読んで理解しておきましょう。
- VueとFirebaseでGoogleアカウントを使ったログインシステムを作る
- Vue3+FirebaseでリアルタイムCRUD操作ができるアプリを作成しよう
- Vue3でVuetifyをインストールしよう
完成したコードはGitHubから確認してください。
https://github.com/DanNakatoshi/Vue-userLogin-Firebase-CRUD
今日の目的
VueとFirebaseを使ってGoogleでログインしているユーザーにデータベースの書き込み権限を与える(記事の投稿など)
プロジェクトのセットアップ
では上記の記事をもとにしてプロジェクトを作成します。
- FirebaseのGoogleプロバイダーによる認証APIの取得
- Vueのプロジェクトの作成
- (必要に応じて)VuetifyなどのUIライブラリのインストール
本日は、Googleの認証システムに集中するので詳しいセットアップやGoogleクラウドの説明は省きます。
では下記のようなコードをApp.vueに記載しましょう。
<template> <v-app id="inspire"> <v-app-bar class="px-3" color="white" flat density="compact" > <v-avatar color="grey-darken-1" size="32" ></v-avatar> <v-spacer></v-spacer> <v-tabs centered color="grey-darken-2" > <v-tab> <router-link to="/">ホーム</router-link> </v-tab> <v-tab v-if="isLoggedIn"> <router-link to="/profile">プロフィール</router-link> </v-tab> </v-tabs> <v-spacer></v-spacer> <v-btn v-if="!isLoggedIn" @click="signInWithGoogle" > サインイン </v-btn> <v-btn v-if="isLoggedIn" @click="handleSignOut" > サインアウト </v-btn> </v-app-bar> <v-main class="bg-grey-lighten-3"> <v-container> <v-row> <v-col cols="12" sm="3" > <v-sheet rounded="lg" min-height="268" > <div class="d-flex justify-center"> <p v-if="displayName" class="pa-3" > {{ displayName }}さん </p> </div> </v-sheet> </v-col> <v-col cols="12" sm="9" > <v-sheet min-height="70vh" rounded="lg" > <div class="pa-1"> <router-view /> </div> </v-sheet> </v-col> </v-row> </v-container> </v-main> </v-app> <!-- <router-view signInWithGoogle="signInWithGoogle" /> --> </template> <script setup> import { onMounted, ref } from 'vue'; import { getAuth, onAuthStateChanged, signOut, GoogleAuthProvider, signInWithPopup, } from 'firebase/auth'; import { useRouter } from 'vue-router'; const router = useRouter(); const isLoggedIn = ref(false); const displayName = ref('ゲスト'); let auth; onMounted(() => { auth = getAuth(); onAuthStateChanged(auth, (user) => { if (user) { isLoggedIn.value = true; displayName.value = user.displayName; console.log(user.uid); } else { isLoggedIn.value = false; displayName.value = 'ゲスト'; } }); }); function handleSignOut() { signOut(auth) .then(() => { console.log('サインアウトしました'); router.push('/'); }) .catch((error) => { console.log(error); }); } function signInWithGoogle() { const provider = new GoogleAuthProvider(); signInWithPopup(auth, provider) .then((result) => { displayName.value = result.user.displayName; router.push('/profile'); }) .catch((error) => {}); } </script>
テンプレート(Template)セクション:
<v-app>
:Vuetifyのv-app
コンポーネントで、アプリケーション全体のコンテナを定義します。<v-app-bar>
:アプリケーションの上部に表示されるナビゲーションバーです。ユーザーがログインしているかどうかによって表示内容が変わります。<v-avatar>
:アバター(ユーザーのアイコン)を表示します。<v-tabs>
:タブを表示し、ルーターリンクを含んでいます。<v-btn>
:ボタンを表示し、ログイン状態によって表示内容が変わります。<v-main>
:アプリケーションのメインコンテンツエリアを定義します。<v-container>
:コンテンツを格納するコンテナです。<v-row>
:行を作成し、グリッドシステムを使用して列を配置します。<v-col>
:列を作成し、グリッドシステムを使用してコンテンツを配置します。
スクリプト(Script)セクション:
import
:Vue.jsとFirebaseの関連するメソッドやモジュールをインポートしています。onMounted
:Vue 3のフック関数であり、コンポーネントがマウントされた後に実行されるコードを定義します。ref
:Vue 3のリアクティブ参照を作成します。getAuth
:Firebase AuthenticationのgetAuth
関数を使用して、認証オブジェクトを取得します。onAuthStateChanged
:認証状態の変更を監視し、ユーザーのログイン状態に応じて表示内容を変更します。signOut
:ユーザーをログアウトさせるためにFirebase AuthenticationのsignOut
関数を使用します。GoogleAuthProvider
:Googleの認証プロバイダーオブジェクトを作成します。signInWithPopup
:Googleのポップアップウィンドウを使用してユーザーをログインさせるためにFirebase AuthenticationのsignInWithPopup
関数を使用します。displayName
:ユーザーの表示名をリアクティブな変数として定義します。handleSignOut
:ログアウト処理を行う関数です。ログアウトが成功した場合、コンソールにメッセージを表示し、ホームページにリダイレクトします。signInWithGoogle
:Googleアカウントでログインするための関数です。Googleのポップアップウィンドウを開き、ログインが成功した場合に表示名を更新し、プロフィールページにリダイレクトします。
このコードは、Vue.jsとFirebaseを組み合わせて、ユーザーのログイン状態に基づいて表示内容を切り替えるシンプルなアプリケーションの一部です。ユーザーがログインしている場合、表示名やプロフィールへのリンクが表示され、ログアウトボタンが表示されます。ログアウト状態では、ログインボタンが表示されます。
では、main.jsはこのようになります。
import { createApp } from 'vue'; import App from './App.vue'; import router from './router'; import { initializeApp } from 'firebase/app'; import './style.css' // import { getAuth } from 'firebase/auth'; // Vuetify import 'vuetify/styles' import { createVuetify } from 'vuetify' import * as components from 'vuetify/components' import * as directives from 'vuetify/directives' const vuetify = createVuetify({ components, directives, }) const firebaseConfig = { apiKey: "AIzaSyC4e_0LxspH9Eoi92hEbyZCCxxxxkuTw", authDomain: "tour-386117.firebaseapp.com", projectId: "tour-386117", storageBucket: "tour-386117.appspot.com", messagingSenderId: "638542335894", appId: "1:638542335894:web:b10c3f97432352bc5e17b7", measurementId: "G-71QMF1RPK1" }; initializeApp(firebaseConfig); // const initFirebase = initializeApp(firebaseConfig); const app = createApp(App); app.use(router); app.use(vuetify); app.mount('#app');
このコードは、Vue.jsを使用して作成されたアプリケーションのエントリーポイントです。以下では、コードの主な機能と各セクションについて説明します。
- モジュールのインポート:
createApp
:Vueアプリケーションを作成するための関数です。App
:アプリケーションのルートコンポーネントです。router
:Vue Routerの設定が含まれたルーターオブジェクトです。initializeApp
:Firebaseアプリケーションを初期化するための関数です。createVuetify
:Vuetifyのインスタンスを作成するための関数です。components
:Vuetifyのコンポーネントの一覧です。directives
:Vuetifyのディレクティブの一覧です。
- Firebaseの設定:
firebaseConfig
:Firebaseプロジェクトの構成情報が含まれています。APIキー、認証ドメイン、プロジェクトIDなどが指定されています。initializeApp(firebaseConfig)
:Firebaseアプリケーションを初期化します。Firebaseプロジェクトとの接続を確立します。
- Vueアプリケーションの作成とマウント:
app
:Vueアプリケーションのインスタンスを作成します。app.use(router)
:Vue Routerをアプリケーションに登録します。app.use(vuetify)
:Vuetifyをアプリケーションに登録します。app.mount('#app')
:アプリケーションを指定した要素(#app
)にマウントします。
※APIキーは環境変数からインポートするようにして他人とシェアはしないように!
では、vue-routerをインストールして、index.jsをこのように書きます。
import { createRouter, createWebHistory } from 'vue-router'; import { getAuth, onAuthStateChanged } from 'firebase/auth'; const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: () => import('../views/Home.vue') }, { path: '/profile', component: () => import('../views/Profile.vue'), meta: { requiresAuth: true }, }, ], }); const getCurrentUser = () => { return new Promise((resolve, reject) => { const removeListener = onAuthStateChanged( getAuth(), // Callback function (user) => { removeListener(); resolve(user); }, reject ); }); }; router.beforeEach(async (to, from, next) => { if (to.matched.some((record) => record.meta.requiresAuth)) { if (await getCurrentUser()) { next(); } else { next('/'); } } else { next(); } }); export default router;
このコードは、Vue Routerを使用してルーティングを設定し、Firebase Authenticationを使用して認証状態を監視するためのルーターファイルです。以下では、コードの主な機能と各セクションについて説明します。
- モジュールのインポート:
createRouter
:Vue Routerのインスタンスを作成するための関数です。createWebHistory
:ブラウザの履歴モードを使用してルーター履歴を作成するための関数です。getAuth
:Firebase Authenticationの認証オブジェクトを取得するための関数です。onAuthStateChanged
:Firebase Authenticationの認証状態の変更を監視するための関数です。
- ルーターの設定:
createRouter
関数を使用してルーターのインスタンスを作成します。createWebHistory
関数を使用して、ブラウザの履歴モードを設定します。routes
オプションには、ルートの定義が含まれています。path
プロパティにはパス、component
プロパティにはコンポーネントが指定されています。meta
オプションは、ルートに関連するメタデータを指定します。ここでは、requiresAuth
メタフィールドがtrueの場合、認証が必要なルートであることを示しています。
- getCurrentUser関数:
getCurrentUser
関数は、認証状態が変更されるたびに呼び出され、現在のユーザーを解決するPromiseを返します。onAuthStateChanged関数を使用して、認証状態の変更を監視し、変更があった場合には解決されるPromiseを返します。
- ルーターナビゲーションガード:
router.beforeEach
メソッドは、ルートナビゲーションが行われる前に実行されるコールバック関数を定義します。- ルートが
requiresAuth
メタフィールドを持っている場合、現在のユーザーを取得し、ユーザーが存在する場合は次の処理に進みます。存在しない場合は、ルート'/'
にリダイレクトします。
- ルーターのエクスポート:
export default router
ステートメントにより、作成したルーターのインスタンスが他のファイルで使用できるようになります。
このコードでは、Vue Routerを使用してアプリケーションのルーティングを設定し、Firebase Authenticationを使用して認証状態を監視し、必要な場合には適切なページにリダイレクトするルーターナビゲーションガードを実装しています。
ではnpm run devのコマンドでログインとログアウトができることと、ユーザー名の取得ができることを確認しましょう!

ここまでできたら、次にFirestoreのデータベースを作成して、デフォルトで表示させるデータとユーザーのプロフィールページを作成してみます。状態管理はPiniaで行うとコードもスッキリするのでまずはPiniaをインストールしましょう。
詳しいPiniaの使い方はこちらの記事を参照して下さい。
ではpinia.jsなど適当なファイルを作成してPiniaを定義していきます。
import { defineStore } from 'pinia'; import { ref } from 'vue'; export const useDataStore = defineStore('data', () => { const userData = ref(null); return { userData, }; });
App.vueではこのようにインポートしてPiniaにユーザーのデータを保管します。
下記の様にdataStoreからPiniaにユーザーのデータを保管させます。
※必要に応じてこの記事を参照してください。
import { useDataStore } from "@/stores/pinia"; const dataStore = useDataStore(); onMounted(() => { auth = getAuth(); onAuthStateChanged(auth, (user) => { if (user) { isLoggedIn.value = true; displayName.value = user.displayName; dataStore.userData = user; // console.log(user.uid); } else { isLoggedIn.value = false; displayName.value = 'ゲスト'; dataStore.userData = null } }); });
次にこちらのGitHunのリポを参考にFirestoreのデータベースに接続して初期のデータをロードできるようにしましょう。
例としてpost(記事)コレクションを作成してGoogleのユーザーに付属するuidの情報を割り当てます。これでどのユーザーが記事を作成したか、またuidが一致するユーザーのみ記事の編集や削除が行えるようにします。
まずはfirestoreをmain.jsでインポートしてイニシャライズします。必要な部分だけ追加してください。
import { initializeApp } from 'firebase/app'; import { getFirestore } from 'firebase/firestore'; const firebaseConfig = { apiKey: 'AIzaSyCJCPiLJI_cxxxxLt-kkm8Pe_9bVG8', authDomain: 'js-hiroba.firebaseapp.com', projectId: 'js-hiroba', storageBucket: 'js-hiroba.appspot.com', messagingSenderId: '221494043726', appId: '1:221494043726:web:ea25e09f171d3769984f1f', measurementId: 'G-XTRC17KCB5', }; const fireApp = initializeApp(firebaseConfig); const db = getFirestore(fireApp); export { db };
次にApp.vueに下記の様なコードを追加します。
import { collection, getDocs } from 'firebase/firestore'; const posts = ref(); onMounted( // Load Initial Data (async () => { const querySnapshot = await getDocs(collection(db, 'post')); let fbPosts = []; querySnapshot.forEach((doc) => { const post= { id: doc.id, title: doc.data().title, content: doc.data().content, }; console.log(`${doc.id} => ${doc.data()}`); fbPosts.push(post); console.log(fbPosts); }); posts.value = fbPosts; })() });
posts
はVue.jsのリアクティブなデータ(ref
)です。ref
はVue.jsでデータを監視し、変更を追跡するために使用されます。
onMounted
はVue.jsのライフサイクルフックの1つであり、コンポーネントがマウントされた後に実行される関数です。つまり、コンポーネントが表示された後に実行される処理を指定するために使用されます。
このコードでは、コンポーネントがマウントされた後に非同期関数が実行されます。非同期関数は即時関数(() => {}
)として定義されています。
即時関数内部では、FirestoreのgetDocs
メソッドを使用して、’post’というコレクション内のドキュメントを取得します。取得したドキュメントはquerySnapshot
というオブジェクトに格納されます。
querySnapshot
オブジェクトは、取得したドキュメントのスナップショットであり、forEach
メソッドを使用して各ドキュメントに対してループ処理を行います。
ループ内部では、各ドキュメントのデータを取得し、post
オブジェクトとして作成します。post
オブジェクトには、ドキュメントのID、タイトル、および完了フラグの情報が含まれます。
また、console.log
を使用してドキュメントのIDとデータをコンソールに表示し、fbPosts
配列にtodo
オブジェクトを追加します。その後、fbPosts
配列もコンソールに表示されます。
最後に、posts.value
にfbPosts
配列を代入します。これにより、Vue.jsのリアクティブなデータであるposts
が更新され、コンポーネント内でこれらのデータを使用できるようになります。
では、実際にFirestoreにデータを作成してみましょう。

ではHome.vueに下記のようなコードを書きPiniaに保管したFirestoreのデータベースを読み込ませます。
<template> <div v-for="post in dataStore.posts" :key="post.id" class="ma-2"> <v-card width="400" :title="post.title" :subtitle="post.userName" :text="post.content" ></v-card> </div> </template> <script setup> import { useDataStore } from "@/stores/pinia"; const dataStore = useDataStore(); </script>
テンプレート部分では、v-for
ディレクティブを使用して、dataStore.posts
内の各要素に対して反復処理を行います。dataStore.posts
は、データストアから取得した投稿データの配列を指します。
v-for
ディレクティブ内では、post
という名前の変数を定義し、dataStore.posts
内の各要素を参照します。:key
ディレクティブは、各要素の一意な識別子であるpost.id
を指定します。これにより、Vue.jsが要素を効率的に追跡し、リレンダーの最適化を行うことができます。
v-card
コンポーネントは、Vue Materialのカードコンポーネントであり、データストア内の各投稿に対して表示されます。:title
、:subtitle
、:text
などのプロパティには、投稿のタイトル、ユーザー名、コンテンツなどのデータがバインドされます。
これでランディングページにForestoreのデータが表示されるようになりました。

プロフィールページで記事の投稿
ではProfile.vueに行きログインしているユーザーが記事を投稿する機能を作成します。
Profile.vue
<template> <v-sheet width="300" class="mx-auto" > <v-form @submit.prevent> <v-text-field v-model="postForm.title" label="タイトル" ></v-text-field> <v-text-field v-model="postForm.content" label="コンテンツ" ></v-text-field> <v-btn type="submit" block class="mt-2" @click="addPost" >Submit</v-btn > </v-form> </v-sheet> </template> <script setup> import { ref, reactive } from 'vue'; import { useDataStore } from '@/stores/pinia'; import { collection, addDoc } from 'firebase/firestore'; import { db } from '@/main.js'; const dataStore = useDataStore(); const postInitial = { title: '', content: '', }; const postForm = reactive({ ...postInitial, }); // フォームをリセットする関数 function resetForm() { Object.assign(postForm, postInitial); } function addPost() { addDoc(collection(db, 'post'), { title: postForm.title, content: postForm.content, uid: dataStore.userData.uid, userName: dataStore.userData.displayName, }); resetForm() } </script>
テンプレート部分では、v-sheet
コンポーネントを使用して、フォームの外側を囲むシートを作成しています。
フォーム部分では、v-form
コンポーネントを使用してフォームを作成しています。
v-text-field
コンポーネントを使用して、タイトルとコンテンツの入力フィールドを作成しています。v-model
ディレクティブを使用して、入力された値をpostForm
オブジェクトの該当するプロパティにバインドしています。
v-btn
コンポーネントは、送信ボタンを表示するために使用されています。type="submit"
によって、ボタンがフォームの送信トリガーとして機能するようになります。@click
イベントリスナーは、ボタンがクリックされたときにaddPost
関数を呼び出します。
useDataStore
関数を使用して、データストアフックをインポートし、dataStore
という変数に割り当てています。これにより、データストアのデータにアクセスできます。
ref
とreactive
を使用して、Vue.jsのリアクティブなデータを作成しています。postForm
はreactive
を使用してオブジェクトをリアクティブにするため、postInitial
オブジェクトを展開して初期値として設定しています。
resetForm
関数は、フォームをリセットするための関数です。postForm
オブジェクトにpostInitial
オブジェクトのプロパティを割り当てることで、フォームの値を初期状態に戻します。
addPost
関数は、投稿を追加するための関数です。addDoc
関数を使用してFirestoreのpost
コレクションに新しいドキュメントを追加します。postForm
オブジェクトの値を使用して、投稿のタイトル、コンテンツ、ユーザーID、ユーザー名などのプロパティを設定します。addDoc
の実行後には、resetForm
関数が呼び出され、フォームがリセットされます。
以上がこのコードの概要です。ユーザーはフォームを使用してタイトルとコンテンツを入力し、送信ボタンをクリックすることで投稿を追加できます。投稿はFirebaseのFirestoreのpost
コレクションに保存されます。

このように記事が投稿され、さらにFirebaseのリアルタイムDBでブラウザをリロードすることなくブラウザのデータを更新することができました。
同じ要領で、DBの削除と編集も行う事ができます。
それぞれのドキュメント(データ)はGoogleユーザーのuidなどで紐づけしておきましょう!
全体的にコードが煩雑になってしまいましたが、firebaseのコードは別途pluginsなどのディレクトリを作成して別で読みこみさせるようにしましょう。
今日はこれくらいで。
お疲れ様です。