はじめに
Googleアカウントのログイン機能付き簡易webサイトを、Vue3とSupabaseを利用して作る方法を説明します。
Vue3で簡単なウェブアプリを作る際に、Googleアカウントでログインできるようにすれば、セキュリティも高まるし、ユーザー情報も使えて便利だなと思ったので、作り方を探しましたが、ハンズオン例が少なかった(Next.js強し…)ので、記事にしてみました。
またログインをできるだけ簡単に実装したかったので、話題のSupabaseに入門してみました。
こんな方にお薦めの記事です
- Vue3でSupabaseのGoogleアカウントの認証を試してみたい方
- Vue3 Composition APIでSupabaseの連携について知りたい方
- アプリのフロントエンドで簡単な認証ページがほしい方
構築に必要なもの
- node.js
- npm
- Googleアカウント
- Google Cloud Platformアカウント
- Supabaseアカウント
前提として、Node.jsのインストールや、GCPとSupabaseのアカウントの作成が必要です。
構築内容について
Vue3
- Vue3はComposition APIを利用
- Typescript不使用
- ルーティング管理のvue router
- 状態管理ライブラリのPinia
Supabase
- Supabase Authを利用
- 認証のみなので、今回はDatabase利用なし
Google Cloud Platform
- プロジェクトの作成
- 認証情報の作成
構築手順
それではやっていきたいと思います。最初にSupabaseとGoogle Cloud Platformの連携から行います。
supabaseでプロジェクト作成
Supabaseを開いて、プロジェクトを作成します。
プロジェクト名、Database Passwordを決めて入力します。作成したプロジェクトはメニューが沢山ありますが、今回は基本的なAPI設定とAuthのわかりやすい機能のみを使いたいと思います。
GCP OAuth同意画面設定
作成したSupabaseのダッシュボード画面とは別ウィンドウで、Google Cloud Platformのコンソール画面を開きます。
新しいプロジェクトを作成します。
プロジェクトに移動し、OAuth同意画面を開き設定をします。
UserTypeには、Google Workspaceのような組織アカウントを利用したい場合はInternal
を、すべてのGoogleアカウントからのログインにはExternal
を選択します。今回はExternalで進めます。
アプリ情報の設定
GCPが作成してくれる同意画面の情報を入力します。アプリ名
、ユーザーサポートメール
、デベロッパーの連絡先情報
は必須項目になっていますので、それぞれ入力してください。
Googleアカウントから取得したい項目の許可を設定するため、以下のスコープを追加します。
- .../auth/userinfo.email
- ...auth/userinfo.profile
- openid
OAuth同意画面を作成した直後はステータスがTesting
になっており、テストユーザーのみがアクセスできるようになっています。本来であればテストユーザーを追加する流れなのだと思いますが、supabaseの場合、テストユーザーを設定せずともログイン出来ているので、テストユーザーの設定はそのまま次に進めます。(本番利用時は公開が必要)
サマリーを確認して完了させます。
GCP 認証情報設定
GCPの認証情報を開きます。
+認証情報を作成
からOAuthクライアントIDを選び、アプリケーションの種類にウェブアプリケーション
を選択し、任意の名前を入力します。
ブラウザの別の画面でSupabaseを開いて、メニューからAuthentication>Prividerを選択します。一覧にGoogleがあるので、メニューを開いて、Callback URL(for OAuth)にあるURLをコピーします。
GCP側の「承認済みのリダイレクト URI」にコピーしたURLを貼り付け、作成ボタンを押します。
作成されたOAuthクライアントからクライアントIDとクライアントシークレットをコピーしておきます。
SupabaseへクライアントIDを登録
SupabaseのAuthentication画面でコピーしたクライアントIDとクライアントシークレットをGoogleの認証設定に貼り付け、Enable Sign in with Google
をONにして保存します。
Googleの項目がEnabled
になっていれば設定完了です。
Vueアプリを構築
Vueアプリを構築していきます。構築にあたってはアールエフェクト様の記事を参考にさせていただきました。
Vueはこのサイトでお勉強させていただきました。初心者の私にもわかりやすく、初学の方にお薦めです。
プロジェクトの作成
npmを使って、ローカル環境にプロジェクトを作成します。ターミナルで下記のコマンドを好きなローカルフォルダで実行します。
npm init vue@latest
対話式のインストールが始まります。
❯ npm init vue@latest
Need to install the following packages:
create-vue@3.10.4
Ok to proceed? (y) y
Vue.js - The Progressive JavaScript Framework
✔ Project name: … vue-googlelogin-app
✔ Add TypeScript? … No
✔ Add JSX Support? … No
✔ Add Vue Router for Single Page Application development? … Yes
✔ Add Pinia for state management? … Yes
✔ Add Vitest for Unit Testing? … No
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No
✔ Add Vue DevTools 7 extension for debugging? (experimental) … No
Scaffolding project in /Users/**/***/vue/dev/vue-googlelogin-app...
Done. Now run:
cd vue-googlelogin-app
npm install
npm run dev
いくつか項目がありますが、Vueルーターを追加する、Add Vue Router for Single Page Application development?
と、状態管理のPiniaを使うためにAdd Pinia for state management?
をYes
で進めました。
VueとSupabaseの接続
作成されたプロジェクトフォルタに移行して初期インストールを行います。
cd vue-googlelogin-app
npm install
次にSupabaseのライブラリを追加します。
npm install @supabase/supabase-js
インストール後、プロジェクトフォルダの直下(package.jsonなどと同じ階層)に.envファイルを作成します。
作成したファイルにSupabaseのプロジェクト画面にある、Project APIからProject URL
とAPI Key
をコピーして、.env
ファイルに保存します。
VITE_SUPABASE_URL=YOUR_SUPABASE_URL # プロジェクトURLをここに
VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY # API Keyをここに
次にSupabaseのクライアントを読み込むためにsrcフォルダへsupabase.js
ファイルを作成します。
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
Piniaにauth.jsを作成
まずは状態管理ライブラリのPiniaのファイルを作成します。PiniaはVueのインストール時に同時にインストールしました。src>stores
にあるデフォルト入っているcounter.js
を削除して、代わりにauth.js
を作成します。
import { defineStore } from 'pinia';
export const useAuthStore = defineStore({
id: 'auth',
state: () => ({
user: null,
}),
getters: {
isLoggedIn: (state) => state.user,
},
actions: {
setUser(user) {
this.user = user;
},
clearUser() {
this.user = null;
},
},
});
ユーザーのログイン状態を保管するために、state
無いにuserを初期値null
で設定しました。
getters
にはisLoggin
が定義されていて、userのログイン状態を返すことで、現在のログイン状態を判断できるようになっています。
action
にはGoogleアカウントでサインインした場合に、userに値をセットするsetUser
とサインアウトした場合にuserの値を削除するclearUser
を定義しています。
これらの設定をuseAuthStore
としてexportして、他のvueファイルよりimportして読み込みます。
App.vueを修正
デフォルトで作成されるApp.vueを修正します。一度、中身をすべて削除し、下記のコードに書き換えます。
<script setup>
import { supabase } from './supabase';
import { useAuthStore } from './stores/auth';
const auth = useAuthStore();
async function fetchUser() {
const { data, error } = await supabase.auth.getSession();
if (error) {
console.error('Error fetching session:', error);
auth.clearUser();
} else {
auth.setUser(data.session?.user ?? null);
}
}
fetchUser();
supabase.auth.onAuthStateChange(async (event, session) => {
if (event === 'SIGNED_IN' && session) {
auth.setUser(session.user);
} else if (event === 'SIGNED_OUT') {
auth.clearUser();
}
});
</script>
<template>
<router-view></router-view>
</template>
scriptタグ内でSupabaseのjsファイルを読み込み、piniaに設定したuseAuthStore
をimportしています。使いやすいようにauth
へuseAuthStore()
を格納しています。
fetchUser()で状態を、onAuthStateChangeでイベントの発生をそれぞれ監視しています。
fetchUser()
fetchUser()
はsupabaseのセッション状態を取得する関数supabase.auth.getSession()
を利用し、ブラウザのローカルストレージに保存されたsupabaseのセッション情報を取得します。ログイン中の場合はauth.setUser()
を利用して、Piniaのuser情報に取得したセッション情報を格納します(もしdataが無かったらnullを代入)。またセッションがなければauth.clearUser()
でユーザー情報を削除します。
supabase.auth.onAuthStateChange
supabase.auth.onAuthStateChange
はサインインとサインアウトのイベントを監視し、SIGNED_IN
イベントが発生した際はsession.userの情報をpiniaのuserへ格納します。SIGNED_OUT
はuser情報をclearUserで削除します。
templateタグ
App.vueファイルのtemplate
タグはVue Routerを利用しているので、ルーティングで設定したVueのページコンポーネントを読み込む内容です。
ここまでで一度npm run dev
で動作確認します。
npm run dev
VITE v5.3.1 ready in 149 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
表示されたURLにブラウザでアクセスします。
現在のrouter/index.js
で設定されている/views/HomeView.vue
の内容が表示されています。エラーが出ていなければ、ここまでの設定はOKです。
ルーティングの設定
src/view
フォルダに以下の3つのファイルを追加します。
- SignIn.vue
- AuthCallback.vue
- DashBoard.vue
ファイルの中身は、あとで変更するので、同じ内容でも構いません。
<template>
<h1>Title</h1>
</template>
ファイルが作成できたら、src/router/index.js
ファイルを編集し、追加したファイルのルーティングを追加します。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/signin',
name: 'signin',
component: () => import('../views/SignIn.vue'), // サインイン画面
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('../views/DashBoard.vue'), // ログイン後のダッシュボード
},
{
path: '/auth/callback',
name: 'authCallback',
component: () => import('../views/AuthCallback.vue'), // 認証後の処理を行うビュー
},
]
})
export default router
http://localhost:5173/signin
などのURLを開いてみて、Title
と表示されれば、ルーティングが正常に稼働しています。
HomeView.vueを修正
ルーティングが出来たので、ルートディレクトリで表示されるHomeView.vueを修正します。
状態管理ライブラリPiniaで定義したuseAuthStoreにある、gettersのisLoggedInを使って、ルートディレクトリ/
へアクセスしたときに表示されるリンクを切り替えています。
<script setup>
import { useAuthStore } from '../stores/auth';
const auth = useAuthStore();
</script>
<template>
<ul>
<li v-if="auth.isLoggedIn">
<router-link to="/dashboard">Dashboard</router-link>
</li>
<li v-if="!auth.isLoggedIn">
<router-link to="/signin">SignIn</router-link>
</li>
</ul>
<h1>Supabase + Vue3 + Pinia</h1>
</template>
<style scoped>
</style>
サインインしていない状態ではuser
の値にはnull
となっているので、isLoggedIn
はfalseとなり、SignIn
のリンクのみが表示されています。
サインイン機能の実装
さきほど/src/viewへ追加したSignIn.vueを下記に置き換えます。
SignIn.vue
<script setup>
import { ref } from 'vue';
import { supabase } from '../supabase';
import { useRouter } from 'vue-router';
const router = useRouter();
const error = ref(null);
const signInWithGoogle = async () => {
const { error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
queryParams: {
prompt: 'select_account'
}
}
});
if (error) {
console.error('Login failed:', error.message);
}
};
</script>
<template>
<div>
<h1>Sign In</h1>
<button @click="signInWithGoogle">Sign in with Google</button>
<p v-if="error">{{ error }}</p>
</div>
</template>
Googleアカウントのサインインボタンを表示する画面になります。
signInWithGoogle
内でsupabase.auth.signInWithOAuth()
を利用して、OAuth認証を行います。認証プロバイダーにgoogle
を指定し、optionsパラメータ内で認証を行った後の遷移先パスをredirectTo
で設定しています。
queryParamsにprompt: 'select_account'
を指定していますが、これはGoogleアカウントのログイン時に毎回ユーザーを選択する画面を表示させるためです。この設定が無いと、毎回同じユーザーでログインするようになります。設定した場合と設定しない場合はコメントアウトなどで試していただけると動作の違いがわかると思います。
AuthCallBack.vue
つぎにログイン処理後の状態に応じて、遷移先の画面を振り分けるAuthCallBack.vue
を変更します。
<script setup>
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { supabase } from '../supabase';
const router = useRouter();
onMounted(async () => {
const { data: { session }, error } = await supabase.auth.getSession();
if (error) {
console.error('認証エラー:', error.message);
router.push({ name: 'signin' });
} else if (session) {
router.push({ name: 'dashboard' });
} else {
router.push({ name: 'home' });
}
});
</script>
<template>
<div>
<h1>認証を処理しています...</h1>
</div>
</template>
supabase.auth.getSession()
でログイン状態を確認して、Vueルーターで画面をリダイレクトさせています。認証に時間がかかる場合のローディング画面のようなものです。ネットワークの状態などによってはほぼ表示されなかったりします。
この時点でGoogleアカウントを使ったログインができるようになります。では実際にログイン処理を行ってみます。
ホーム画面のSignIn
を押して、SignIn.vue
を表示させます。
Sign in with Google
ボタンを押します。
Googleのアカウント選択画面が表示されましたので、アカウントをクリックします。
Googleアカウントへの接続同意画面が表示されました。内容を確認して次へ
を押します。
ログインに成功していると、URLがDashboard
に切り替わります。
URLを直接入力して、画面を切り替えてHOMEに戻ると、SignInのリンクがなくなり、Dashboardへのリンクのみになっています。
Dashboard.viewの修正
ログインした後のDashboardでGoogleアカウントから取得した内容を表示させます。またログアウトボタンもつ追加します。下記の内容に/src/view/DashBoard.vueを変更します。
<script setup>
import { onMounted, ref, onUnmounted } from 'vue';
import { supabase } from '../supabase';
import { useRouter } from 'vue-router';
const router = useRouter();
const user = ref(null);
const handleSignOut = async () => {
try {
const { error } = await supabase.auth.signOut();
if (error) throw error;
router.push({
name: 'home',
});
} catch (error) {
alert(error.message);
}
};
async function getUser() {
const { data, error } = await supabase.auth.getUser();
if (error) {
console.error('Error fetching user:', error.message);
} else {
console.log(data); // デバッグ用
user.value = data.user;
}
}
onMounted(async () => {
await getUser();
});
</script>
<template>
<h1>DashBoard</h1>
<div v-if="user">
<button @click="handleSignOut">SignOut</button>
</div>
<p>Welcome to our service</p>
<div v-if="user">
<p><strong>Name:</strong> {{ user.user_metadata.full_name }}</p>
<p><strong>Email:</strong> {{ user.email }}</p>
<img v-if="user.user_metadata.avatar_url" :src="user.user_metadata.avatar_url" alt="User Avatar">
</div>
<div>
</div>
</template>
ユーザー情報を取得するためのgetUser()
とサインアウトボタン用にhandleSignOut
を定義しています。
先程のhttp://localhost:5173/dashboardを表示させます。
Googleアカウントに設定している名前、メールアドレス、アカウントの画像が表示されました。
SingOutボタンをおしてGoogleアカウントをログアウトさせます。
SignInが表示されたホーム画面へ戻りました。
ここままででGoogleアカウントのログインとログアウトはできましたが、DashboardへはURLを直接指定すれば入れてしまいますので、Vue routerへ設定を追加します。
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
+ import { useAuthStore } from '../stores/auth'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/signin',
name: 'signin',
component: () => import('../views/SignIn.vue'), // サインイン画面
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('../views/DashBoard.vue'), // ログイン後のダッシュボード
+ meta: {
+ requiresAuth: true,
+ },
},
{
path: '/auth/callback',
name: 'authCallback',
component: () => import('../views/AuthCallback.vue'), // 認証後の処理を行うビュー
},
]
});
+router.beforeEach((to) => {
+ const auth = useAuthStore();
+ if (!auth.isLoggedIn && to.meta.requiresAuth) {
+ return { name: 'signin' };
+ }
+});
export default router
Piniaを利用し、Vueルーターの動作前にログイン状態をチェックします。Vue Routerのナビゲーションガードという機能を利用し、ルートの定義にmetaフィールドを追加し、requiresAuth: true
の設定があるルーティングでログイン状態ではないときに、サインイン画面にリダイレクトさせています。
これでURLを直接指定しても、ログインしていないときはDashborad画面には入れないようになりました。
最後にnpm run build
でdistフォルダーへ出力すれば完成です。
あとはDashbordへ機能を追加したり、新しくルーティングを追加していけば、Googleアカウントを利用したアプリを作れますね。
まとめ
Vue3とSupabaseを組み合わせたGoogleアカウントでログインするサイトのデモをご紹介しました。
Supabaseは勢いがあるためにアップデートが早く、サンプルコードを試しても、すでに動かなくなっているケースも多々見られました。もし当コードも動作しないようでしたらご指摘いただけると助かります。
これからVue3とSupabaseの連携を初めたい方の一助になれば幸いです。
最後に
今回デザインはまったく触れていないので、殺風景なアプリとなってしまいましたが、次はVue3でtailwindを利用する方法もやってみたいと思います。
またユーザーごとで出力内容を切り替えるなどもあれば、本格的にアプリとしても用途が見えてきますので、そちらも見ていきたいなと思います。
ありがとうございました。
参考