フリーランチ食べたい

No Free Lunch in ML and Life. Pythonや機械学習のことを書きます。

Nuxt/VuexでFirebase Authenticationを使ってユーザー認証機能を作る

Nuxt/VuexでFirebase Authenticationを使ったユーザー認証機能を作るにあたって、基本的なやり方はFirebaseドキュメントに書いてあるのですが、「どこに何を書けばいいのか」がよくわからなかったので調べながらまとめてみました。

f:id:ikedaosushi:20190417172517p:plain

完成形

こんな感じで用意されたUIを使ってログイン機能が実装できます。 f:id:ikedaosushi:20190417190410g:plain

前提条件

Firebase Projectの作成/Firebase SDKの作成はNuxt/Vuexには関係しない部分なので次の記事を参考に行ってください。この記事はFirebase SDKをNuxt/Vuexにどう組み込むか、を中心に書きたいと思います。

firebase.google.com

環境

  • nuxt@2.5.1
  • vue@2.6.10
  • vuex@3.1.0
  • firebase@5.9.2
  • firebaseui@3.5.2

FirebaseUIインストール&CSS読み込み

ログイン画面を簡単に実装するために、Firebase SDK用に用意されたUI KitのFirebaseUI用います。

github.com

次のコマンドでインストールして、

npm install firebaseui --save 
# yarn add firebaseui

cssはnuxt.config.jsで読み込んでおきます。

nuxt.config.js

  css: [
    "firebaseui/dist/firebaseui.css"
  ],

pluginで初期化

firebaseの初期化処理はに書くと切り出せて便利です。 次のような感じです。

plugins/firebase.js

import firebase from 'firebase/app';
import 'firebase/auth';
import config from '~/firebase.config';

if (!firebase.apps.length) {
  firebase.initializeApp(config);
}

export const authProviders = {
  // 使うものだけ定義しておきましょう
  Email: firebase.auth.EmailAuthProvider.PROVIDER_ID,
  Google: firebase.auth.GoogleAuthProvider.PROVIDER_ID,
  Facebook: firebase.auth.FacebookAuthProvider.PROVIDER_ID,
  Twitter: firebase.auth.TwitterAuthProvider.PROVIDER_ID,
  Github: firebase.auth.GithubAuthProvider.PROVIDER_ID
};

export const auth = firebase.auth();

firebaseの接続設定もgitginoreしやすいようにconfigファイルに切り出します。

firebase.config

export default {
  apiKey: "xxxxx",
  authDomain: "xxxxx",
  databaseURL: "xxxxx",
  projectId: "xxxxx",
  storageBucket: "xxxxx",
  messagingSenderId: "xxxxx"
}

Storeの作成

認証された情報を管理するためにVuexのStoreを使います。 Storeのモジュールモードの方で書いています。logoutアクションで今作成したプラグインを使っています。

store/auth.js

import { auth } from '~/plugins/firebase'

export const state = () => ({
    status: "",
    token: localStorage.getItem('token') || '',
    username: ""
})

export const getters = {
    isLoggedIn: state =>  state.status === "loggedIn"
}

export const actions = {
    gotUser({ commit }, user) {
        commit("setUser", user)
    },
    logout({ commit }) {
        auth.signOut().then(() => {
            commit("logout")
        })
    }
}

export const mutations = {
    setUser(state, user) {
        state.status = "loggedIn"
        state.username = user.displayName
    },
    logout(state) {
        state.status = "loggedOut"
        state.username = ""
    }
}

ミドルウェアでログインのハンドリング

今回は「もしログインしていなかったらログインページにリダイレクトする」という処理をいれました。 onAuthStateChanged はログイン/ログアウトなどの認証情報の変化で発火するので、これをミドルウェアに書いておくことで認証情報に関係する処理を集約することができます。 ログインした場合は、その情報をStoreに書き込むようにします。

middleware/authenticated.js

import { auth } from '~/plugins/firebase'

export default function ({ route, store, redirect }) {
  auth.onAuthStateChanged((user) => {
    if (user) {
      store.dispatch("auth/gotUser", user)
    } else {
      if(route.name !== "login") redirect("/login")
    }
  })
}

nuxt.config.jsに設定することで全ページで有効になります。

nuxt.config.js

  router: {
    middleware: ['authenticated']
  },

コンポーネント

それではログインボタンなどを表示するUI部分です。 今までロジックをプラグイン/ミドルウェアに切り出してきたのでUIに関する設定のみになります。

components/FirebaseAuth.vue

<template lang="pug">
  #firebaseui-auth-container
</template>

<script>
import { auth, authProviders } from '~/plugins/firebase'
import firebaseui from 'firebaseui'

export default {
  name: 'FirebaseAuth',
  mounted() {
    auth.onAuthStateChanged(user => {
      if (!user) {
        const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(auth)

        const config = {
          signInOptions: [
            authProviders.Email,
            authProviders.Google,
            authProviders.Facebook,
          ],
          callbacks: {
            // Nuxtのローカルサーバーで起こるCORSエラーのためこのように設定。本番環境では不要
            signInSuccessWithAuthResult: (authResult) => {
              window.location.href = "/"
              return false // falseを設定するとsignInSuccessUrlにはリダイレクトしない
            }
          },
          signInSuccessUrl: '/',
          signInFlow: 'popup', // ログインフロー設定。Nuxtのローカルサーバーで起こるCORSエラーがあるのでpopupがオススメです。
        }

        ui.start('#firebaseui-auth-container', config)
      }
    })
  }
}
</script>

一点注意点としてはこちらです。

// Nuxtのローカルサーバーで起こるCORSエラーのためこのように設定。本番環境では不要
signInSuccessWithAuthResult: (authResult) => {
  window.location.href = "/"
  return false # falseを設定するとsignInSuccessUrlにはリダイレクトしない
}

signInSuccessWithAuthResultを設定しないとsignInSuccessUrlに自動でリダイレクトされるのですが、Nuxtのローカルサーバーを使っている場合はCORSのエラーが出てリダイレクトできないと思います。CORSのエラーの解消は色々調べたのですが、Nuxtのローカルサーバーだと見つけられず、 一旦 window.location.href で移動してしまっています。ここは改善できそうなポイントですね。

ページから呼び出す

あとはページからコンポーネントを呼び出します。 今回は、ログインボタンのみ表示するので本当にコンポーネントを呼び出すだけ、ですね。

pages/login.js

<template lang="pug">
  firebase-auth
</template>

<script>
import FirebaseAuth from '@/components/FirebaseAuth';
import { mapState, mapGetters, mapActions } from "vuex";

export default {
  name: 'Login',
  components: {
    FirebaseAuth
  }
}
</script>

ここまで上手く行っていれば次のように表示され、ログインボタンを押すとログインできると思います。

f:id:ikedaosushi:20190417200311p:plain

[おまけ] ユーザー名表示&ログアウト

ここまででログイン部分は完成しているのですが、おまけとしてその情報の利用方法とログアウトボタンの実装も書いておきます。 ヘッダーにユーザー名とログアウトボタンを表示してみましょう。

components/Header.vue

<template lang="pug">
  v-toolbar
    v-spacer
    v-toolbar-items.hidden-sm-and-down
      v-btn(flat='') {{username}}
      v-btn(flat='' v-if="isLoggedIn" @click="logout") Logout
</template>

<script>
import { mapState, mapGetters, mapActions } from "vuex";

export default {
  computed: {
    ...mapState("auth", [
      "username"
    ]),
    ...mapGetters("auth", [
      "isLoggedIn"
    ])
  },
  methods: {
    ...mapActions("auth", ["logout"])
  }
}
</script>

うまくいくと次のように表示され、ログアウトボタンを押すと動作すると思います。

f:id:ikedaosushi:20190417200700p:plain

まとめ

ちょっと長くなりましたが、Nuxt/VuexでFirebase Authenticationを使ったユーザー認証機能を作る方法についてまとめました。 FirebaseとNuxtの組み合わせで従来大変だったユーザーの認証管理が簡単に実装でき、素晴らしいなと思います! これよりもっと良い書き方があったり、別の方法があったら是非教えてください。

コードの全体を見たい方が入れば↓のリポジトリを参照してください。趣味プロジェクトです。

github.com