Nuxt.jsプロジェクトでFirebase AuthenticationのGoogle認証でログインし、認証情報をLocal Storageに保存する

はじめに

この記事ではFirebase AuthenticationのGoogle認証を利用してログイン機能を作成し、そのログイン情報をブラウザのLocal Storageに保存する方法を解説します。

この記事で作成したNuxt.jsプロジェクトは以下のGitHubリポジトリーで公開しています。

GitHub - whitia/nuxt-firebase-sample

Local Storageについて

Local Storageとはその名の通り、ローカル(ブラウザ)にあるストレージのことです。Local Storageの特徴は大きく2つあります。

  1. 保存したデータは半永久的に保持される
  2. ドメインごとにデータ領域が存在する

保存したデータは半永久的に保持される

保存したデータはページをリロードしても、ブラウザを終了させても、PCをシャットダウンしても、半永久的に消えることはありません。

クッキーの場合は有効期限というものが存在していましたが、Local Storageには有効期限という概念はありません。明示的に消さない限りブラウザに残り続けます。

これにより、例えばログイン情報をLocal Storageに保存しておくことによって、能動的にログアウトなどしてLocal Storageのデータを削除しない限り、ログイン状態を保持しておくことが可能になります。

ドメインごとにデータ領域が存在する

Local Storageのデータ領域はドメインごとに分けられています。

例えばAというサイトでログイン情報をLocal Storageに保存した場合、BというサイトからAというサイトのログイン情報は参照できません。

Local Storageはキーと値の組み合わせでデータを保存しますが、キーの一意性はドメイン内で保たれていればいいということになります。

準備

Firebaseの登録、プロジェクト作成、アプリ作成を行っておいてください。Nuxt.jsプロジェクトにFirebaseを導入するには以下の記事を参照してください。

Nuxt.jsプロジェクトでFirestoreを利用したデータの永続化/取り出しの実装方法 - AUTOVICE

また、Firebase AuthenticationのGoogle認証を有効化しておいてください。手順については以下のページを参照してください。

JavaScript で Google ログインを使用して認証する  |  Firebase

実装

それではさっそく実装していきたいと思います。実装の流れは以下のとおりです。

  1. ログイン機能:認証情報をVueストアに保存
  2. 認証情報をLocal Storageに保存
  3. ログアウト機能:認証情報を破棄

ログイン機能:認証情報をVueストアに保存

今回はヘッダーコンポーネントに一連の機能を作っていきます。components/Header.vueを新規作成し、以下のように実装してください(※スクリプトタグを書くとコードが消えてしまうので書いていません、コピペする際は補完お願いします)。

<template>
  <b-navbar variant="faded" type="light">
    <div class="container">
      <b-navbar-brand>
        Nuxt-Firebase-Sample
      </b-navbar-brand>
      <b-navbar-nav class="ml-auto">
        <template v-if="$store.getters.getAuth.uid === null">
          <a href="#" @click.prevent="login()">
            Login with Google
          </a>
        </template>
        <template v-else>
          <b-nav-item-dropdown right>
            {{ $store.getters.getAuth.name }}
          </b-nav-item-dropdown>
        </template>
      </b-navbar-nav>
    </div>
  </b-navbar>
</template>

<!-- スクリプト -->
export default {
  methods: {
    login() {
      this.$store.dispatch('login')
    }
  }
}

リンクをクリックするとloginメソッドを呼び出し、さらにVuexストアのloginアクションを呼び出しています。

次にstore/index.jsを新規作成し、以下のように実装します。

import firebase from '~/plugins/firebase'

export const state = () => ({
  auth: {
    uid: null,
    name: null
  }
})

export const actions = {
  login({ commit }) {
    return new Promise((resolve, reject) => {
      const provider = new firebase.auth.GoogleAuthProvider()

      firebase.auth().signInWithPopup(provider)
      .then(result => {
        commit('setAuth', result.user)
        resolve(true)
      })
      .catch(error => {
        console.error('An error occurred in login(): ', error)
        reject(error)
      })
    })
  }
}

export const mutations = {
  setAuth(state,auth) {
    state.auth.uid = auth.uid
    state.auth.name = auth.displayName
  }
}

export const getters = {
  getAuth(state) {
    return state.auth
  }
}

Firebase AuthenticationのGoogleプロバイダでログイン処理を呼び出しています。ログインに成功したらVuexストアのデータ領域に認証情報(UIDとログイン名)を保存します。

Vuexストアに認証情報を保存したことでログイン状態がわかるようになりました。

認証情報をLocal Storageに保存

認証情報をVuexストアに保存しましたが、Vuexストアのデータ領域はページをリロードすると消えてしまいます。ページをリロードしても(ブラウザを終了させても)認証情報を保持しておくためにはLocal Storageを使います。

ところで、Local Storageというのはブラウザの機能です。一方、Nuxt.jsは基本的にSSR(サーバーサイドレンダリング)のため、サーバーでDOMを構築します。つまり、サーバーで構築したDOMがクライアントのブラウザに送信されるまでLocal Storageにはアクセスできません。

JavaScriptでLocal Storageにアクセスするには以下のように書きます。

window.localStorage.set('key', 'value');
window.localStorage.get('key');

このwindowがブラウザのことを指しますが、サーバーにいる間はwindowは存在しないためエラーになってしまいます。

ではどうすればいいかというと、vue-localStorageというライブラリを使います。このライブラリを使うことでサーバーでDOMの構築をしている間にもLocal Storageにアクセスできるようになります。

以下のコマンドを実行してライブラリをインストールします。

$ npm install vue-localstorage --save

次にplugins/vue-localStorage.jsを新規作成し、以下を記述します。

import Vue from 'vue'
import VueLocalStorage from 'vue-localstorage'

Vue.use(VueLocalStorage)

最後にnuxt.config.jsに以下を追記します。

  plugins: [
    // 以下を追記
    '~/plugins/vue-localStorage'
  ],

これでどのコンポーネントからでもvue-localStorageが呼び出せるようになったので、components/Header.vueを修正していきましょう。

<template>
  <b-navbar variant="faded" type="light">
    <div class="container">
      <b-navbar-brand>
        Nuxt-Firebase-Sample
      </b-navbar-brand>
      <b-navbar-nav class="ml-auto">
        <template v-if="$store.getters.getAuth.uid === null">
          <a href="#" @click.prevent="login()">
            Login with Google
          </a>
        </template>
        <template v-else>
          <b-nav-item-dropdown right>
            {{ $store.getters.getAuth.name }}
          </b-nav-item-dropdown>
        </template>
      </b-navbar-nav>
    </div>
  </b-navbar>
</template>

<!-- スクリプト -->
export default {
  data() {
    return {
      keys: {
        uid: 'nfs_auth_uid',
        name: 'nfs_auth_name'
      }
    }
  },
  mounted() {
    const auth = {
      uid: this.$localStorage.get(this.keys.uid, null),
      displayName: this.$localStorage.get(this.keys.name, null)
    }

    this.$store.commit('setAuth', auth)
  },
  methods: {
    login() {
      this.$store.dispatch('login')
      .then(() => {
        const auth = this.$store.getters.getAuth

        this.$localStorage.set(this.keys.uid, auth.uid)
        this.$localStorage.set(this.keys.name, auth.name)
      })
    }
  }
}

ログイン後、認証情報をLocal Storageに保存しています。ページ読み込み時には逆にLocal Storageから認証情報を取り出し、Vuexストアに保存し直しています。

ブラウザで動作を確認してみてください。ログイン後、ページをリロードしても、ブラウザを再起動してもログイン状態が保持されているはずです。

ログアウト機能:認証情報を破棄

最後に残ったログアウト機能は簡単です。Vuexストアのデータ領域とLocal Storageの認証情報を破棄すればいいだけです。components/Header.vueに以下を追記します。

...

        <template v-if="$store.getters.getAuth.uid === null">
          <a href="#" @click.prevent="login()">
            Login with Google
          </a>
        </template>
        <template v-else>
          <b-nav-item-dropdown right>
            <template v-slot:button-content>
              {{ $store.getters.getAuth.name }}
            </template>
            <!-- 以下を追記 -->
            <b-dropdown-item href="#" @click.prevent="logout()">Logout</b-dropdown-item>
          </b-nav-item-dropdown>
        </template>

...

<!-- スクリプト -->
export default {
  ...

  methods: {
    ...

    // 以下を追記
    logout() {
      this.$localStorage.remove(this.keys.uid)
      this.$localStorage.remove(this.keys.name)

      this.$store.commit('setAuth', { uid: null, name: null })
    }
  }
}

まとめ

Nuxt.jsプロジェクトでFirebase AuthenticationのGoogle認証でログインし、認証情報をLocal Storageに保存する方法を解説しました。

自前でログイン機能を実装するのはセキュリティのことを考えると実はハードルが高いことです。万が一セキュリティに穴があり、ユーザー情報が漏洩してしまっては元も子もありません。

GoogleやTwitterといった信頼できるプロバイダのログイン機能を利用すれば、実装の手間も省けるしセキュリティも確保できるし、良いこと尽くしです。ユーザーの目線からでも信頼されやすいと思います。

さらに認証情報をデータベースや外部サービスに保存するのではなくLocal Storageに保存することで情報漏えいのリスクを低減させることができます。

小規模なWebアプリであればこの方法がベストプラクティスなのではないかと思います。