LoginSignup
1
2
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

Vue3とSupabaseで作るGoogleアカウント認証サイト

Last updated at Posted at 2024-06-23

はじめに

Googleアカウントのログイン機能付き簡易webサイトを、Vue3Supabaseを利用して作る方法を説明します。

Vue3で簡単なウェブアプリを作る際に、Googleアカウントでログインできるようにすれば、セキュリティも高まるし、ユーザー情報も使えて便利だなと思ったので、作り方を探しましたが、ハンズオン例が少なかった(Next.js強し…)ので、記事にしてみました。

またログインをできるだけ簡単に実装したかったので、話題のSupabaseに入門してみました。

こんな方にお薦めの記事です

  • Vue3SupabaseのGoogleアカウントの認証を試してみたい方
  • Vue3 Composition APISupabaseの連携について知りたい方
  • アプリのフロントエンドで簡単な認証ページがほしい方

構築に必要なもの

  • node.js
  • npm
  • Googleアカウント
  • Google Cloud Platformアカウント
  • Supabaseアカウント

前提として、Node.jsのインストールや、GCPとSupabaseのアカウントの作成が必要です。

構築内容について

Vue3

  • Vue3Composition APIを利用
  • Typescript不使用
  • ルーティング管理のvue router
  • 状態管理ライブラリのPinia

Supabase

  • Supabase Authを利用
  • 認証のみなので、今回はDatabase利用なし

Google Cloud Platform

  • プロジェクトの作成
  • 認証情報の作成

構築手順

それではやっていきたいと思います。最初にSupabaseとGoogle Cloud Platformの連携から行います。

supabaseでプロジェクト作成

Supabaseを開いて、プロジェクトを作成します。

Supabase.png

プロジェクト名、Database Passwordを決めて入力します。作成したプロジェクトはメニューが沢山ありますが、今回は基本的なAPI設定とAuthのわかりやすい機能のみを使いたいと思います。

GCP OAuth同意画面設定

作成したSupabaseのダッシュボード画面とは別ウィンドウで、Google Cloud Platformのコンソール画面を開きます。

新しいプロジェクトを作成します。

新しいプロジェクト_–_ダッシュボード.png

プロジェクトに移動し、OAuth同意画面を開き設定をします。

gcp_menu.png

UserTypeには、Google Workspaceのような組織アカウントを利用したい場合はInternalを、すべてのGoogleアカウントからのログインにはExternalを選択します。今回はExternalで進めます。

Cursor_と_API_とサービス_–mt-supabase-auth-de…–_Google_Cloud_コンソール.png

アプリ情報の設定

GCPが作成してくれる同意画面の情報を入力します。アプリ名ユーザーサポートメールデベロッパーの連絡先情報は必須項目になっていますので、それぞれ入力してください。

API_とサービス.png

Googleアカウントから取得したい項目の許可を設定するため、以下のスコープを追加します。

- .../auth/userinfo.email
- ...auth/userinfo.profile
- openid

スコープ更新.png

OAuth同意画面を作成した直後はステータスがTestingになっており、テストユーザーのみがアクセスできるようになっています。本来であればテストユーザーを追加する流れなのだと思いますが、supabaseの場合、テストユーザーを設定せずともログイン出来ているので、テストユーザーの設定はそのまま次に進めます。(本番利用時は公開が必要)

テストユーザー.png

サマリーを確認して完了させます。

サマリー.png

GCP 認証情報設定

GCPの認証情報を開きます。

+認証情報を作成からOAuthクライアントIDを選び、アプリケーションの種類にウェブアプリケーションを選択し、任意の名前を入力します。

認証情報_–_API_とサービス.png

Create_OAuth_client_ID_–_API_とサービス.png

ブラウザの別の画面でSupabaseを開いて、メニューからAuthentication>Prividerを選択します。一覧にGoogleがあるので、メニューを開いて、Callback URL(for OAuth)にあるURLをコピーします。

Cursor_と_Authentication___Supabase.png

GCP側の「承認済みのリダイレクト URI」にコピーしたURLを貼り付け、作成ボタンを押します。

redirect_url.png

作成されたOAuthクライアントからクライアントIDとクライアントシークレットをコピーしておきます。

clientcode.png

SupabaseへクライアントIDを登録

SupabaseのAuthentication画面でコピーしたクライアントIDとクライアントシークレットをGoogleの認証設定に貼り付け、Enable Sign in with GoogleをONにして保存します。

Authentication___Supabase.png

Googleの項目がEnabledになっていれば設定完了です。

Cursor_と_Authentication___Supabase.png

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 URLAPI Keyをコピーして、.envファイルに保存します。

api.png

.env
VITE_SUPABASE_URL=YOUR_SUPABASE_URL # プロジェクトURLをここに
VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY # API Keyをここに

次にSupabaseのクライアントを読み込むためにsrcフォルダへsupabase.jsファイルを作成します。

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を作成します。

src/stores/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を修正します。一度、中身をすべて削除し、下記のコードに書き換えます。

src/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しています。使いやすいようにauthuseAuthStore()を格納しています。

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にブラウザでアクセスします。

Cursor_と_Vite_App.png

現在の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ファイルを編集し、追加したファイルのルーティングを追加します。

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と表示されれば、ルーティングが正常に稼働しています。

Vite_App.png

HomeView.vueを修正

ルーティングが出来たので、ルートディレクトリで表示されるHomeView.vueを修正します。

状態管理ライブラリPiniaで定義したuseAuthStoreにある、gettersのisLoggedInを使って、ルートディレクトリ/へアクセスしたときに表示されるリンクを切り替えています。

/src/view/HomeView.vue
<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のリンクのみが表示されています。

Vite_App.png

サインイン機能の実装

さきほど/src/viewへ追加したSignIn.vueを下記に置き換えます。

SignIn.vue

/src/view/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を変更します。

/src/view/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を表示させます。

Vite_App.png

Sign in with Googleボタンを押します。

ログイン_-_Google_アカウント.png

Googleのアカウント選択画面が表示されましたので、アカウントをクリックします。

ログイン_-_Google_アカウント.png

Googleアカウントへの接続同意画面が表示されました。内容を確認して次へを押します。

2024_06_21_16_07.png

ログインに成功していると、URLがDashboardに切り替わります。

2024_06_21_16_12.png

URLを直接入力して、画面を切り替えてHOMEに戻ると、SignInのリンクがなくなり、Dashboardへのリンクのみになっています。

Dashboard.viewの修正

ログインした後のDashboardでGoogleアカウントから取得した内容を表示させます。またログアウトボタンもつ追加します。下記の内容に/src/view/DashBoard.vueを変更します。

/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を表示させます。

2024_06_21_16_19.png

Googleアカウントに設定している名前、メールアドレス、アカウントの画像が表示されました。

SingOutボタンをおしてGoogleアカウントをログアウトさせます。

Vite_App.png

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を利用する方法もやってみたいと思います。
またユーザーごとで出力内容を切り替えるなどもあれば、本格的にアプリとしても用途が見えてきますので、そちらも見ていきたいなと思います。

ありがとうございました。

参考

1
2
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
1
2