VueとFirebaseでGoogleアカウントを使ったログインシステムを作る

前回はGoogleが提供するFirebaseのサービス(Baas:Backend as a Service)を紹介しました。

今日は、このFirebaseとJavaScriptのフレームワークのVueを使ってユーザーのログインシステムを作ってみましょう!

まずは、基本となるメールアドレスを使ってのログインのロジックを理解することが重要になります。順に従って進めていくことをお勧めします。

完成したコードはGitHubからどうぞ。

環境

なぜGoogleアカウントを使ってログインを作る必要があるか

Googleアカウントである必要はありません。Twitterでも、GitHubアカウントでもOKです。数年前から見かけるようになったGoogleアカウントを使ってログインなどの認証方法はOAuthと言います、

OAuth方法を使用することで下記のメリットがあります。

  • アプリケーションにユーザー登録、パスワードリセットなどのロジックを作る必要がなくなる
  • 自作のログインシステムだとセキュリティの新しい基準に毎回合わせる必要がある
  • 最新のセキュリティでユーザーのログインを守ることができる
  • ユーザーがアプリごとにパスワードを記録する必要がなくなる
  • サインアップの手順が簡単、Googleなどを信用しているので不信感を持たれない

以上のことから開発のスピードが速くなることとセキュリティが強化されることが理解できます。

Firebaseは必要か

詳しいFirebaseの説明は前回の記事を参照して下さい。Firebaseはあくまでもオプショナルです。しかし、無料でバックエンドのサービスを始められること、簡単にOAuthのロジックをフロントエンドに組み込められることから使っていて損はないシステムだと思います。またクライアント側はVueで作成するので必要に応じてバックエンド側のみ、フロントエンド側のみのテクノロジーを変更することができるのもメリットになります。

Vueプロジェクトの作成

今回はViteのビルドツールを使用してVueアプリを作成します。詳しいViteの使い方はこちらを参照してください。

npm create vite@latest

✔ Project name: … google-signin
✔ Select a framework: › Vue
✔ Select a variant: › JavaScript

Scaffolding project in /home/dan/Documents/google-signin...

Done. Now run:

  cd google-signin
  npm install
  npm run dev

ではテキストエディタを開いて基本のページを作成していきます。

Emailを使ったユーザー登録

では参考例として、Emailを使用したユーザー登録を作成していきます。その後にGoogleアカウントを使用したユーザーログインを作成します。

まずは、srcフォルダにFeed.vue、Home.vue、Register.vue,SignIn.vueを作成します。Feed.vueはログインしたユーザーのみアクセスさせるように進めていきます。

├── src
│   ├── App.vue
│   ├── assets
│   │   └── vue.svg
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.js
│   ├── style.css
│   └── views
│       ├── Feed.vue
│       ├── Home.vue
│       ├── Register.vue
│       └── SignIn.vue

次にURLのラウティングを行うためにvue-routerをインストールします。下記のコマンドを実行してください。

npm install vue-router@4

#結果
added 2 packages, and audited 35 packages in 4s
6 packages are looking for funding
  run `npm fund` for details
found 0 vulnerabilities

次にsrc/routerフォルダを作成しindex.jsファイルを作成します。

├── src
│   ├── App.vue
│   ├── assets
│   │   └── vue.svg
│   ├── main.js
│   ├── router
│   │   └── index.js

ではrouter/indewx.jsに下記の様にページとなるコンポーネントを登録していきます。

import { createRouter, createWebHistory } from "vue-router";

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {path: '/', component: ()=> import('../views/Home.vue')},
    {path: '/register', component: ()=> import('../views/Register.vue')},
    {path: '/sign-in', component: ()=> import('../views/SignIn.vue')},
    {path: "/feed", component: ()=> import('../views/Feed.vue')},
  ],
});

export default router;

つぎに作成しtarouterをmain.jsにインポートして読み込ませます。

import { createRouter, createWebHistory } from 'vue-router';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: () => import('../views/Home.vue') },
    { path: '/register', component: () => import('../views/Register.vue') },
    { path: '/sign-in', component: () => import('../views/SignIn.vue') },
    { path: '/feed', component: () => import('../views/Feed.vue') },
  ],
});

export default router;

次にデフォルトでついてきたHelloWorld.vueを削除して、App.vueに行きます。

App.vueにrouter-viewを登録してURLごとに読み込まれるページViewをレンダーさせます。

App.vue

<script setup>
</script>

<template>
  <router-view />
</template>

では、各Home、SignIn、Register、Feedのコンポーネントに適当なHTMLを入れてサーバーを起動するとURLごとにViewが変わることが確認できますね。

では最後にナビゲーションバーを作成して、URLではなくボタンからユーザーがページにジャンプできるようにします。

App.vue

<template>
  <nav>
    <router-link to="/">ホーム</router-link> |
    <router-link to="/feed">フィード</router-link> |
    <router-link to="/register">登録</router-link> |
    <router-link to="/sign-in">サインイン</router-link>
  </nav>
  <router-view />
</template>

ここまで問題がないことを確認してください。

Firebaseのプロジェクトを作成

ではFirebaseのプロジェクトを作成していきます。詳しくはFirebaseを始めようの記事を参考にしてください。

手順だけ説明します。

  • Firebase公式サイトにアクセス
  • 使ってみるをクリック
  • プロジェクトを追加をクリック
  • プロジェクト名を入力(なんでもいい)
  • フォームに従いFirebaseを追加(無料プランであることを確認)
  • ダッシュボードのアプリにFirebaseを追加して利用開始のメッセージ下の</>のアイコンからウェブをクリック
  • アプリの登録をする。
  • FirebaseSDKの追加タブでJavaScriptのスニペットが表示されるのでコピーしておく。

下記のコマンドでfirebaseのライブラリをインストールします。

npm install firebase

下記のようなコードがでるのでコピーしておきます。

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: "AIzaSyDJC81zlbkl3p0BillgZW9EQCjRAgH1ruk",
  authDomain: "asameshi-380120.firebaseapp.com",
  projectId: "asameshi-380120",
  storageBucket: "asameshi-380120.appspot.com",
  messagingSenderId: "878490165462",
  appId: "1:878490165462:web:115d595950e47321826977"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

認証システムを設定する

firebaseの構築タブからAuthenticationを選択します。

始めるのボタンを押します。

Emailを追加します。

次にGoogleを追加します。

これで2つのプロバイダが登録されました。

では、npm install firebaseのコマンドを実行していることを再度確認してテキストエディタに戻ります。

次に先ほどコピーしておいたfirebaseのスニペットをmain.jsに貼り付けます。

initializeApp(firebaseConfig);の部分だけ変更してOKです。

import { createApp } from 'vue';
import './style.css';
import App from './App.vue';
import router from './router';
import { initializeApp } from "firebase/app";

const firebaseConfig = {
  apiKey: "AIzaSyDJC81zlbkl3p0BillgZW9EQCjRAgH1ruk",
  authDomain: "asameshi-380120.firebaseapp.com",
  projectId: "asameshi-380120",
  storageBucket: "asameshi-380120.appspot.com",
  messagingSenderId: "878490165462",
  appId: "1:878490165462:web:115d595950e47321826977"
};

initializeApp(firebaseConfig);
const app = createApp(App);

app.use(router);
app.mount('#app');

認証システムを追加する

では下準備が整ったところでいよいよユーザー認証のロジックをコードにしていきます。

①ユーザー登録(Register.vue)

<template>
  <h1>あさめしアプリのアカウントを作成しよう</h1>
  <p><input type="text" placeholder="メールアドレス" v-model="email" /></p>
  <p><input type="password" placeholder="パスワード" v-model="password" /></p>
  <p><button @click="register" >登録</button></p>
  <p><button @click="signInWithGoogle">Googleでサインインする</button></p>
</template>

<script setup>
import {
  getAuth,
  createUserWithEmailAndPassword,
} from 'firebase/auth';
import { ref } from 'vue';
import { useRouter } from 'vue-router';

const email = ref('');
const password = ref('');
const router = useRouter();

const auth = getAuth();
function register() {
  createUserWithEmailAndPassword(auth, email.value, password.value)
    .then((userCredential) => {
      console.log(auth.currentUser);
      router.push('/feed');
    })
    .catch((error) => {
    });
}
</script>

以上のようなテンプレートを作成します。

Vueの基本が理解されていることを前提に進めています。もしVueの基本を学びたい方はこちらのVue講座を読んでください。

今回注目してほしいところは、firebaseのライブラリからgetAuthとcreateUserWithEmailAndPasswordをインポートしてユーザー登録を完了させ、firebaseにデータを飛ばしてあげたことです。

ではfirebaseに戻り、Usersタブを見ると新しいユーザーが登録されていることが分かります。

もちろん実際には、強いパスワードのみ許可し、パスワードの再確認のインプットも作成すべきですが、今回は省きます。

ログインしたユーザーはconsole.log(auth.currentUser);の部分のように見ることができます。このデータはブラウザのLocal Storageに保管されています。

ユーザーのログイン

ではユーザーのログインのロジックを作成していきます。内容は登録の時とほぼ同じになりますが、ログインできなかった時のエラーだけパターンを作成しておくことになります。Firebaseではログインが出来なかったときに下記のようなパターンのエラーメッセージを返してくれます。

  • 認証できないエラー、メールアドレスが違うエラー
  • ユーザーが存在しないエラー
  • パスワードが違うエラー
  • ユーザーがDisable(利用不可)になっている場合

では、下記のようにログインのコードをSignIn.vueに書いていきます。

<template>
  <h1>あさめしアプリにログイン</h1>
  <p><input type="text" placeholder="メールアドレス" v-model="email" /></p>
  <p><input type="password" placeholder="パスワード" v-model="password" /></p>
  <p v-if="errMsg">{{ errMsg }}</p>
  <p><button @click="login" >ログイン</button></p>
  <p><button @click="signInWithGoogle">Googleでサインインする</button></p>
</template>

<script setup>
import {
  getAuth,
  signInWithEmailAndPassword,
} from 'firebase/auth';
import { ref } from 'vue';
import { useRouter } from 'vue-router';

const email = ref('');
const password = ref('');
const router = useRouter();
const errMsg = ref('');

const auth = getAuth();
function login() {
  signInWithEmailAndPassword(auth, email.value, password.value)
    .then((userCredential) => {
      console.log(auth.currentUser);
      router.push('/feed');
    })
    .catch((error) => {
      console.log(error.code)
      switch (error.code) {
        case "auth/invalid-email":
          errMsg.value = "メールアドレスが不正です";
          break;
        case "auth/user-not-found":
          errMsg.value = "ユーザーが見つかりません";
          break;
        case "auth/wrong-password":
          errMsg.value = "パスワードが間違っています";
          break;
      }
    });
}
</script>

ユーザーのログアウト

ログアウトはFirebaseを使う事でとても簡単にできてしまいます。

まずは、ナビゲーションバーにサインアウトのボタンを追加します。

  • ユーザーのステータスが変更した場合にisLoggedInの値を変更する
  • handleSignOut関数でfirebaseのサインアウトメソッドを発火させる

ではApp.vueに上記のロジックを書いていきます。

<template>
  <nav>
    <router-link to="/">ホーム</router-link> |
    <router-link to="/feed">フィード</router-link> |
    <router-link to="/register">登録</router-link> |
    <router-link to="/sign-in">サインイン</router-link> |
    <button @click="handleSignOut" v-if="isLoggedIn">サインアウト</button>
  </nav>
  <router-view />
</template>

<script setup>
import { onMounted, ref } from 'vue';
import { getAuth, onAuthStateChanged, signOut } from 'firebase/auth';
import { useRouter } from 'vue-router';

const router = useRouter();
const isLoggedIn = ref(false);

let auth;
onMounted(()=> {
  auth = getAuth();
  onAuthStateChanged(auth, (user) => {
    if (user) {
      isLoggedIn.value = true;
    } else {
      isLoggedIn.value = false;
    }
  });
});

function handleSignOut() {
  signOut(auth).then(() => {
    console.log('サインアウトしました');
    router.push('/sign-in');
  }).catch((error) => {
    console.log(error);
  });
}
</script>

Feedページをログインしたユーザーのみにアクセスさせる

ではFeed.vueのページをログインしたユーザーだけがアクセスできるようにします。

vue-routerのbeforeEach()を使用することでrouteの前にこのロジックを設定できます。このことをnavigation guradsといいます。

では、router/index.jsに下記のrouter.beforeEachとrequireAuthを追加します。

import { createRouter, createWebHistory } from 'vue-router';
import { getAuth } from 'firebase/auth';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: () => import('../views/Home.vue') },
    { path: '/register', component: () => import('../views/Register.vue') },
    { path: '/sign-in', component: () => import('../views/SignIn.vue') },
    {
      path: '/feed',
      component: () => import('../views/Feed.vue'),
      meta: { requiresAuth: true },
    },
  ],
});


router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (getAuth().currentUser) {
      next();
    } else {
      next('/');
    }
  } else {
    next();
  }
});


export default router;

これで、ログインしているユーザだけフィードのページが見れるようになりました。

しかし!フィードのURLのhttp://localhost:5173/feedでページをリロードすると一瞬ホームにリダイレクトされていしまいます。これはページがマウントされたときにfirebaseのcurrentUserが存在していないからです。ではこれを修正していきます。

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: '/register', component: () => import('../views/Register.vue') },
    { path: '/sign-in', component: () => import('../views/SignIn.vue') },
    {
      path: '/feed',
      component: () => import('../views/Feed.vue'),
      meta: { requiresAuth: true },
    },
  ],
});

const getCurrentUser = () => {
  return new Promise((resolve, reject) => {
    const removeListener = onAuthStateChanged(
      getAuth(),
      (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;

まずonAuthStateChangedをfirebaseからインポートします。

次にgetCurrentUser関数を作成します。この関数はPromiseオブジェクトを返します。

最後に、router.beforeEachメソッドをAsyncに変え、awaitでgetCurrentuser()の結果を待つようにしています。

これでフィードページでリロードをしてもページがそのままになるようになりました。

Google OAuthを追加

では、やっと本題のGoogleアカウントでのログインを追加しましょう。

Register.vueにGoogleAuthProviderとsignInWithPopupをインポートします。

次に下記のsignInWIthGoogle関数を追加してGoogleでログインボタンに追加します。

<template>
  <h1>あさめしアプリのアカウントを作成しよう</h1>
  <p><input type="text" placeholder="メールアドレス" v-model="email" /></p>
  <p><input type="password" placeholder="パスワード" v-model="password" /></p>
  <p><button @click="register" >登録</button></p>
  <p><button @click="signInWithGoogle">Googleでサインインする</button></p>
</template>

<script setup>
import {
  getAuth,
  createUserWithEmailAndPassword,
  GoogleAuthProvider,
  signInWithPopup,
} from 'firebase/auth';
import { ref } from 'vue';
import { useRouter } from 'vue-router';

const email = ref('');
const password = ref('');
const router = useRouter();

const auth = getAuth();
function register() {
  createUserWithEmailAndPassword(auth, email.value, password.value)
    .then((userCredential) => {
      console.log(auth.currentUser);
      router.push('/feed');
    })
    .catch((error) => {
    });
}

//追加した部分
function signInWithGoogle() {
  const provider = new GoogleAuthProvider();
  signInWithPopup(auth, provider)
    .then((result) => {
      console.log(result);
      router.push('/feed');
    })
    .catch((error) => {
    });
}
</script>

これでボタンをクリックするとポップアップが表示されGoogleでログインすることができましたね!

ここで、firebaseのダッシュボードを見るとGoogleでユーザー登録ができたことが分かります。

このようにFirebaseを使う事で最新の認証システムが簡単にできるので便利ですね。他にもできることがたくさんあるのでFirebaseダッシュボードを時間をかけて確認してください。

もし、この記事がお役に立てたら是非コメントください。

では、お疲れ様でした。