Blog

Firebase Authentication と NUXT(vue.js)で簡単にユーザー登録&認証を作成する

なかなか記事を書く時間が取れず、久々の更新です。 現在、FirebaseとNUXTを使用して新しいサービスを開発中で、ユーザー登録と認証を実装中にアレ?っと思ったことがありました。

Firebase と NUXTのキーワードでGoogle検索をすると、いろいろなところで実装方法などが紹介されています。実際に僕もいくつか参考にさせていただきましたが、ほぼ全てのところで、ユーザーの認証状態の管理にvuexとvuex-persistedstateを使用するとありました。これによってブラウザのリロードで初期化されるvuexの値をlocalStorageに保存して永続化させることができるというものでしたが、nuxt-linkで遷移するとlocalStorageの値が参照できずログインページにリダイレクトされるという不具合に遭遇し、どうもうまく動きませんでした。

結論から言うと、middlewareを使用してFirebase側でユーザーの認証状態の監視を行えば、vuex-persistedstateを使用せずともブラウザをリロードしても認証状態を参照することができるようになるので、vuex-persistedstateは不要と言うことになり、vuex-persistedstateの使用を止めたら正常に動作するようになりました。

というわけで、今回は、備忘録的な役割も含め、簡単にSNSアカウントを使用したソーシャルログインなどが実装できるFirebase AuthenticationとVue.jsのフレームワークであるNUXTを使用して実際にユーザー登録と認証ができるところまでやってみようと思います。

NUXT のインストール

まず最初にNUXTのインストールから始めます。NUXTにはcreate-nuxt-appというインストール用のツールが用意されているので、そちらを使用します。

公式のドキュメントにもありますが、npmもしくはyarnで実行します。 ちなみに僕は不都合がない限りnpmを使用しています。(ただの慣れです。)

npm 使用の場合

npx create-nuxt-app <project-name>

yarn 使用の場合

yarn create nuxt-app <project-name>

ここでは、プロジェクト名は、test-firebase-authとしています。コマンドを実行するといくつか質問されますが、基本的にそのままEnterキーを押してもらって問題ないですが、このチュートリアルでは、UIの描画にVuetifyを使用しているので、Choose UI frameworkでは、Vuetify.jsを選択しておいた方が分かりやすいです。

Choose Next.js modules では、使用するモジュールをスペースキーで選択してEnterキーを押します。

Choose listing tools こちらも使用したいツールをスペースキーで選んでEnterを押します。

Choose test framework テストをする場合、好みのツールを選択します。

Choose rendering mode 今回は、Firebaseと連携したユーザー登録と認証なので、Single Page App を選択します。

最後の質問に答えてEnterキーを押すとインストールが開始されます。 最終的にこんな画面が表示されたらインストール成功です。

続いてFirebaseのインストール

npm 使用の場合

npm install firebase --save 

yarn 使用の場合

yarn add firebase save

これで準備ができたので、Firebaseのコンソールで各種設定をしていきます。

Firebaseにプロジェクトの追加とアプリの登録をする

初めてFirebaseを使用する方は、無料のSparkプランで十分ですので、まず登録をしてください。

まずプロジェクトを追加します。任意の名前で良いのでプロジェクトを追加します。その後、アプリの追加(ウェブ)をします。今回は、ユーザー登録と認証までの実装になるので、Firebase Hostingにはチェックを入れていません。

アプリの登録が完了するとNUXTからFirebaseへ接続する際に必要になるコードが表示されます。このキャプチャには、プロジェクトの作成時にアナリティクスを使用する選択をしたので、アナリティクス関連のコードが表示されています。

表示されたコードのうち、以下の部分を後ほど使用しますので、コピーしておきます。

apiKey: “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”,
authDomain: “xxxxxxxxxxxx.firebaseapp.com”,
databaseURL: “https://xxxxxxxxxxx.firebaseio.com”,
projectId: “xxxxxxxxxxxxxxxxx”,
storageBucket: “xxxxxxxxxxxxxx.appspot.com”,
messagingSenderId: “000000000000”,
appId: “0:0000000000:web:xxxxxxxxxxxxxxxxxxx”,
measurementId: “X-XXXXXXXXXXXX”

Firebaseコンソールで必要な認証方法の設定

次にFirebaseコンソールで使用する認証方法の設定を行います。今回は、よくあるパターンとしてメール、Google、Facebook、Twitterの認証を実装したいと思います。

AuthenticationページでSign-in methodのタブに遷移します。

それぞれ、クリックすると設定画面が開きますので、有効化していきます。 FacebookおよびTwitterに関しては、それぞれの開発者用ウェブサイトでアプリケーションの登録や申請が必要です。そこで入手可能なアプリケーションIDやアプリシークレットを使用して有効化します。また、各SNSの開発者用ウェブサイトの利用には、開発者登録が必要です。

FacebookやTwitterの開発者用ウェブサイトでの設定フローは割愛しますが、問題なく設定が完了すれば、以下のようにメール、Google、Facebook、Twitterのステータスが有効となります。

NUXTでFirebaseの設定をする

NUXTでFirebaseが使用できるように設定を行います。 まず plugins ディレクトリ配下に firebase.js を作成します。

firebase.js を plugins ディレクトリ配下に作成

先ほどコピーしたapiKeyなどの情報を使用して以下の内容で作成します。 そのままコピーした情報を指定いただいても構いませんが、このチュートリアルでは、dotenvを使用してるので、firebase.jsは、以下のようになっています。apiKeyなどは、別途.envファイルで設定します。

import firebase from ‘firebase’

const config = {
    apiKey: process.env.FIREBASE_API_KEY,
    authDomain: process.env.FIREBASE_AUTH_DOMAIN,
    databaseURL: process.env.FIREBASE_DATABASE_URL,
    projectId: process.env.FIREBASE_PROJECT_ID,
    storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.FIREBASE_MESSSAGE_SENDER_ID,
    appId: process.env.FIREBASE_APP_ID,
    measurementId: process.env.FIREBASE_MEASUREMENT_ID
}

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

export const auth = firebase.auth
export default firebase

ファイルを作成したら nuxt.config.js のpluginsセクションにfirebase.jsを追記してFirebaseを使用できるようにしておきます。

    plugins: [
        '~/plugins/firebase.js'
    ],

.envファイルを使用する場合

firebase.jsに直接記述せずに.envファイルで指定する場合は、apiKeyなどの情報は、プロジェクト直下に.envの名前でファイルを作成して以下のように記述しておきます。xになっている箇所は、実際のものに置き換えてください。

BASE_URL=http://localhost:3000
FIREBASE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
FIREBASE_AUTH_DOMAIN=xxxxxxxxxxxxxxxxxxxxxxxxx
FIREBASE_DATABASE_URL=xxxxxxxxxxxxxxxxxxxxxx
FIREBASE_PROJECT_ID=xxxxxxxxxxxxxxx
FIREBASE_STORAGE_BUCKET=xxxxxxxxxxxxxxxxxxxxx
FIREBASE_MESSSAGE_SENDER_ID=xxxxxxxxxxxxx
FIREBASE_APP_ID=x:xxxxxxxxxxxxxxxxx:x:xxxxxxxxxxxxxxxxx
FIREBASE_MEASUREMENT_ID=x-xxxxxxxxxxx

firebase.auth.js を plugins ディレクトリ配下に作成

これでNUXTからFirebaseを操作する準備ができましたがpluginsディレクトリ配下にもう一つファイルを作成します。

onAuthStateChangedというメソッドは、ユーザーの認証状態が変化する度に実行されるので、それを利用してユーザーの認証状態を監視してvuexと連携できるように、以下の内容を firebase.auth.js としてpluginsディレクトリ配下に保存してください。

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

export default (context) => {
    const { store } = context

    return new Promise((resolve, reject) => {
        auth().onAuthStateChanged(user => {
            //本来は、ここで必要なユーザー情報のオブジェクトを作成して
            //ユーザー情報としてセットする方が好ましいですが、
            //サンプルなので、全てセットしています。
            store.commit('setUser', user)
            resolve()
        })
    })
}

ファイルを作成したら nuxt.config.js のpluginsセクションにfirebase.auth.jsも追記して使用できるようにしておきます。

  plugins: [
    '~/plugins/firebase.js',
    '~/plugins/firebase.auth.js'
  ],

これで、ユーザーがログインやログアウトをした際にvuexに認証状態を通知することができるようになりました。

vuexでユーザー認証の状態を管理する

Vue.jsには、vuexというステート(状態)を管理できる機能が備わっています。 NUXTでもvuexをデフォルトでサポートしていますので簡単に使用することができます。

NUXTでvuexを使用するには、storeディレクトリに設定ファイルを置けば自動的に使用できるようになります。

Firebaseのユーザー認証のステートを管理するvuexの設定ファイル

以下の内容をstoreディレクトリにindex.jsのファイル名で保存します。

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

export const strict = false

export const state = () => ({
    user: null,
})

export const mutations = {
    setUser(state, payload) {
        state.user = payload
    }
}

export const actions = {
    signUp({ commit }, { email, password }) {
        return auth().createUserWithEmailAndPassword(email, password)
    },

    signInWithEmail({ commit }, { email, password }) {
        return auth().signInWithEmailAndPassword(email, password)
    },

    signInWithTwitter({ commit }){
        return auth().signInWithPopup(new auth.TwitterAuthProvider())
    },

    signInWithFacebook({ commit }){
        return auth().signInWithPopup(new auth.FacebookAuthProvider())
    },

    signInWithGoogle({ commit }){
        return auth().signInWithPopup(new auth.GoogleAuthProvider())
    },

    signOut() {
        return auth().signOut()
    }
}

export const getters = {
    user(state){
        return state.user
    },
    isAuthenticated (state) {
        return !!state.user
    }
}

actions内には、メール、Facebook、Google、Twitterと必要な認証メソッドを記述します。認証メソッドに関しては、公式ドキュメントに詳しく記載されているので、ご参照ください。

Auth | JavaScript SDK | Firebase

これで、ユーザー認証の状態がvuexに保存されるようになります。 NUXT内で、 store.getters.isAuthenticated のようにすると認証状態を真偽値で取得することができます。

未ログイン時にログインページにリダイレクトさせるため、middlewareを作成する

このままだと、認証自体は動作しますが、ログアウト状態でも認証が必要なページを閲覧してしまうことができます。

そこで、全てのページでユーザーのログイン状態をチェックしてログインしていなければ、ログインページへ、ログイン済みの場合は、ダッシュボードなど特定のページへリダイレクトさせるmiddlewareを作成します。

middlewareディレクトリにauthenticated.jsとして保存します。

export default ({ store, route, redirect }) => {

    if (!store.getters.isAuthenticated && route.name !== 'login' && route.name !== 'register') {
        redirect('/login')
    }
    if (store.getters.isAuthenticated && (route.name === 'login' || route.name === 'register')) {
        redirect('/')
    }
}

middlewareをNUXTで使用する

middlewareを使用する方法は、全ページでmiddlewareを有効化する方法とログインを必要とするページごとに有効化する2種類の方法があります。

ページごとに有効化する場合

ページごとに有効化する場合は、 vueファイルのscriptタグ内に以下のようの記述すればOKです。

<script>
export default {
  middleware: ‘authenticated’
}
</script>

全ページで有効化する場合

全ページで有効化する場合は、nuxt.config.jsに以下を追記すればOKです。

router: {
    middleware: 'authenticated'
}

フロントUI を構築

この記事の最後にサンプルコードがダウンロードできるようにgithubへのリンクを貼っておくので、結果だけ欲しい人は、このセクションは読み飛ばしてください。

layout/default.vue

動作サンプル的なものなので、特に凝った要素は必要ありません。layout/default.vueを以下のようにシンプルに書き換えます。

<template>
  <v-app>
    <v-container>
      <v-row wrap justify="center" align="center">
        <v-main>
          <v-container>
            <nuxt />
          </v-container>
        </v-main>
      </v-row>
      <v-footer app>
        <span>© {{ new Date().getFullYear() }}</span>
      </v-footer>
    </v-container>
  </v-app>
</template>

index.vue

認証後のページとして作成します。 ログアウトボタンを用意して、メソッドにsignOutを作成してログアウトできるようにしています。signOutメソッドは、vuexのsignOutアクションを実行、そして実行後にログインページにリダイレクトするだけのシンプルなものです。

<template>
  <v-container>
    <v-row wrap justify="center" align="center">
      <v-col :sm="12" :md="8">
        <p class="text-center">ログインユーザのみ閲覧可能なコンテンツ</p>
        <div class="pa-5">
          <v-btn
            block
            color="indigo darken-1"
            nuxt
            to="/sample"
            class="white--text"
          >
            サンプルページへ
          </v-btn>
        </div>
        <div class="pa-5">
          <v-btn
            block
            outlined
            color="grey darken-3"
            @click="signOut"
          >
            ログアウト
          </v-btn>
        </div>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  methods: {
    signOut: function(err) {
      this.$store
        .dispatch('signOut')
        .then(() => {
          this.$router.push({
            name: 'login'
          })
        })
        .catch((err) => {
          alert(err.message)
        })
    }
  }
}
</script>

sample.vue

このページは、不要かもしれませんが、nuxt-linkの動作確認用に作成。

<template>
  <v-container>
    <v-row wrap justify="center" align="center">
      <v-col :sm="12" :md="8">
        <p class="text-center">ログインユーザのみ閲覧可能なコンテンツ</p>
        <p class="text-center">サンプルページ</p>
        <div class="pa-5">
          <v-btn
            block
            color="indigo darken-1"
            nuxt
            to="/"
            class="white--text"
          >
            トップページへ
          </v-btn>
        </div>
        <div class="pa-5">
          <v-btn
            block
            outlined
            color="grey darken-3"
            @click="signOut"
          >
            ログアウト
          </v-btn>
        </div>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  methods: {
    signOut: function(err) {
      this.$store
        .dispatch('signOut')
        .then(() => {
          this.$router.push({
            name: 'login'
          })
        })
        .catch((err) => {
          alert(err.message)
        })
    }
  }
}
</script>

login.vue

ログインページです。シンプルにメールアドレスに入力欄とパスワード欄があります。その下にソーシャルログインボタンを配置しています。

フォーム送信時には、メールアドレス認証用のvuexアクションが実行されるようにします。

ソーシャルログインボタンに関しては、メールアドレス認証のユーザー登録画面でも使用するので、コンポーネントにしています。

<template>
    <v-container>
        <v-row justify=“center">
            <v-col sm="12" md="5">
                <h2 class="text-center subtitle-1 font-weight-bold mb-2">
                    メールアドレスでログイン
                </h2>
                <v-row>
                    <v-col>
                        <v-tabs
                            v-model="tab"
                            background-color="transparent"
                            color="blue accent-2"
                            grow
                            class="mb-3"
                        >
                            <v-tab to="/login">ログイン</v-tab>
                            <v-tab to="/register">アカウント登録</v-tab>
                        </v-tabs>

                        <v-row>
                            <v-col sm="12">
                                <v-card flat>
                                    <v-card-text class="pa-0">
                                        <v-form
                                            ref="login_form"
                                            v-model="login_valid"
                                            lazy-validation
                                        >
                                            <v-text-field
                                                v-model="login_email"
                                                label="メールアドレス"
                                                required
                                            />

                                            <v-text-field
                                                v-model="login_password"
                                                label="パスワード"
                                                required
                                                :append-icon="
                                                    show_loginpassword
                                                        ? 'mdi-eye'
                                                        : 'mdi-eye-off'
                                                "
                                                :type="
                                                    show_loginpassword
                                                        ? 'text'
                                                        : 'password'
                                                "
                                                @click:append="
                                                    show_loginpassword = !show_loginpassword
                                                "
                                            />

                                            <v-alert
                                                v-if="loginErrorMsg"
                                                dense
                                                text
                                                type="error"
                                            >
                                                {{ loginErrorMsg }}
                                            </v-alert>

                                            <v-btn
                                                :disabled="!login_valid"
                                                color="blue darken-3"
                                                class="my-4 white--text"
                                                @click="email_login"
                                            >
                                                ログイン
                                            </v-btn>
                                        </v-form>
                                    </v-card-text>
                                </v-card>
                            </v-col>
                        </v-row>
                        <v-divider class="my-8" />
                        <v-row>
                            <v-col sm="12">
                                <h2
                                    class="text-center subtitle-1 font-weight-bold mb-2"
                                >
                                    その他のアカウントでログイン
                                </h2>
                            </v-col>
                        </v-row>
                        <v-alert
                            v-if="socialLoginErrorMsg"
                            dense
                            text
                            type="error"
                            dismissible
                        >
                            {{ socialLoginErrorMsg }}
                        </v-alert>
                        <SocialLogin />
                    </v-col>
                </v-row>
            </v-col>
        </v-row>
    </v-container>
</template>

<script>
import SocialLogin from '~/components/SocialLogin.vue'

export default {
    layout: 'signin',
    components: {
        SocialLogin
    },
    data: function() {
        return {
            tab: null,
            login_valid: true,
            login_email: '',
            login_password: '',
            show_loginpassword: false,
            loginErrorMsg: '',
            socialLoginErrorMsg: ''
        }
    },
    methods: {
        email_login: function(err) {
            this.$store
                .dispatch('signInWithEmail', {
                    email: this.login_email,
                    password: this.login_password
                })
                .then(() => {
                    this.login_email = ''
                    this.login_password = ''
                    this.$router.push({
                        name: 'index'
                    })
                })
                .catch((err) => {
                    if (err.code === 'auth/user-disabled') {
                        this.loginErrorMsg =
                            'このアカウントはロックされています。'
                    } else {
                        this.loginErrorMsg =
                            'メールアドレスまたはパスワードが間違っています。'
                    }
                })
        }
    }
}
</script>

register.vue

メール認証を使用するユーザー用の登録画面です。 メールアドレスとパスワード、パスワード確認用フィールドを用意します。フォーム送信時にvuexのユーザー登録アクションが実行されるようにします。

メールアドレスの未入力や入力間違いなどは、エラーが表示されるようにvuetifyのバリデーションに基づいて記述を追加しています。

下部には、ソーシャルログインボタン群も表示するようにしています。

<template>
    <v-container>
        <v-row justify="center">
            <v-col sm="12" md="5">
                <h2 class="text-center subtitle-1 font-weight-bold mb-2">
                    メールアドレスで登録
                </h2>
                <v-row>
                    <v-col>
                        <v-tabs
                            v-model="tab"
                            background-color="transparent"
                            color="blue accent-2"
                            grow
                            class="mb-3"
                        >
                            <v-tab to="/login">ログイン</v-tab>
                            <v-tab to="/register">アカウント登録</v-tab>
                        </v-tabs>

                        <v-row>
                            <v-col sm="12">
                                <v-card flat>
                                    <v-card-text class="pa-0">
                                        <v-form
                                            ref="register_form"
                                            v-model="register_valid"
                                            lazy-validation
                                        >
                                            <v-text-field
                                                v-model="register_email"
                                                label="メールアドレス"
                                                :rules="emailRules"
                                                required
                                                validate-on-blur
                                            />

                                            <v-text-field
                                                ref="register_password"
                                                v-model="register_password"
                                                label="パスワード"
                                                required
                                                :append-icon="
                                                    show_registerPassword
                                                        ? 'mdi-eye'
                                                        : 'mdi-eye-off'
                                                "
                                                :type="
                                                    show_registerPassword
                                                        ? 'text'
                                                        : 'password'
                                                "
                                                :rules="register_passwordRules"
                                                validate-on-blur
                                                loading
                                                @click:append="
                                                    show_registerPassword = !show_registerPassword
                                                "
                                            >
                                                <template v-slot:progress>
                                                    <v-progress-linear
                                                        :value="score.value"
                                                        :color="score.color"
                                                        absolute
                                                        height="2"
                                                    />
                                                </template>
                                            </v-text-field>
                                            <v-text-field
                                                v-model="
                                                    register_password_again
                                                "
                                                label="パスワード(確認)"
                                                required
                                                :append-icon="
                                                    show_registerPassword
                                                        ? 'mdi-eye'
                                                        : 'mdi-eye-off'
                                                "
                                                :type="
                                                    show_registerPassword
                                                        ? 'text'
                                                        : 'password'
                                                "
                                                validate-on-blur
                                                :rules="
                                                    register_passwordAgainRules
                                                "
                                                @click:append="
                                                    show_registerPassword = !show_registerPassword
                                                "
                                            />

                                            <v-alert
                                                v-if="registerErrorMsg"
                                                dense
                                                text
                                                type="error"
                                            >
                                                {{ registerErrorMsg }}
                                            </v-alert>

                                            <v-btn
                                                :disabled="!register_valid"
                                                color="blue darken-3"
                                                class="mr-4 white--text"
                                                @click="email_register"
                                            >
                                                登録
                                            </v-btn>
                                        </v-form>
                                    </v-card-text>
                                </v-card>
                            </v-col>
                        </v-row>
                        <v-divider class="my-8" />
                        <v-row>
                            <v-col sm="12">
                                <h2
                                    class="text-center subtitle-1 font-weight-bold mb-2"
                                >
                                    その他のアカウントでログイン
                                </h2>
                            </v-col>
                        </v-row>
                        <SocialLogin />
                    </v-col>
                </v-row>
            </v-col>
        </v-row>
    </v-container>
</template>

<script>
import SocialLogin from '~/components/SocialLogin.vue'
import firebase from '@/plugins/firebase'
import zxcvbn from 'zxcvbn'
import { mapActions, mapState, mapGetters } from 'vuex'

export default {
    layout: 'signin',
    components: {
        SocialLogin
    },
    data: function() {
        return {
            registerErrorMsg: '',
            tab: null,
            register_valid: true,
            register_email: '',
            register_password: '',
            register_password_again: '',
            emailRules: [
                (v) => {
                    if (v) {
                        return (
                            /.+@.+\..+/.test(v) ||
                            '有効なメールアドレスを入力してください'
                        )
                    }else{
                        return true
                    }
                }
            ],
            register_passwordRules: [
                (v) => !!v || 'パスワードを入力してください',
                (v) =>
                    zxcvbn(v).score >= 3 ||
                    '大文字・小文字・数字・記号を混ぜた強いパスワードにしてください'
            ],
            register_passwordAgainRules: [
                (v) => {
                    if (v) {
                        return (
                            this.$refs.register_password.value === v ||
                            'パスワードと一致しません'
                        )
                    }else{
                        return true

                    }
                }
            ],
            show_registerPassword: false
        }
    },
    computed: {
        progress() {
            return this.score.value
        },
        score() {
            const result = zxcvbn(this.register_password)

            switch (result.score) {
                case 4:
                    return {
                        color: 'green',
                        value: 100
                    }
                case 3:
                    return {
                        color: 'light-green lighten-1',
                        value: 75
                    }
                case 2:
                    return {
                        color: 'amber accent-2',
                        value: 50
                    }
                case 1:
                    return {
                        color: 'deep-orange lighten-1',
                        value: 25
                    }
                default:
                    return {
                        color: 'red darken-3',
                        value: 0
                    }
            }
        }
    },
    methods: {
        email_register: function(err) {
            if (this.$refs.register_form.validate()) {
                this.$store
                    .dispatch('signUp', {
                        email: this.register_email,
                        password: this.register_password
                    })
                    .then(() => {
                        this.register_email = ''
                        this.register_password = ''
                        this.$router.push({
                            name: 'index',
                            params: {
                                dashboard_msg: true,
                                dashboard_msg_text:
                                    'アカウントの登録が完了しました。'
                            }
                        })
                    })
                    .catch((err) => {
                        console.log(err)
                        if (err.code === 'auth/email-already-in-use') {
                            this.registerErrorMsg =
                                'このメールアドレスは既に登録されています。'
                        } else if (err.code === 'auth/invalid-email') {
                            this.registerErrorMsg = '無効なメールアドレスです。'
                        } else {
                            this.registerErrorMsg =
                                'エラーにより登録できませんでした。'
                        }
                    })
            }
        }
    }
}
</script>

components/SocialLogin.vue

ログインページとユーザー登録ページ、2つのページで使用するため、コンポーネントにして2つのページから読み込むようにします。

各ソーシャルログインボタン押下時には、vuexの規定のアクションが実行されるようにします。

エラー発生時にFirebaseから返されるエラーコードの一覧は、公式ドキュメントに記載されているので、それらを活用して状況に合わせたエラーメッセージが表示されるようにします。

Admin Authentication API エラー{. external_link}

<template>
    <v-row justify="center">
        <v-col sm="12">
            <v-btn
                block
                class="color-twitter text-capitalize mb-3"
                @click="twitterLogin"
            >
                <v-icon left class="color-twitter__icon" size="22">
                    mdi-twitter
                </v-icon>
                Twitterアカウントでログイン
            </v-btn>
            <v-btn
                block
                class="color-facebook text-capitalize mb-3"
                @click="facebookLogin"
            >
                <v-icon left class="color-facebook__icon" size="22">
                    mdi-facebook
                </v-icon>
                Facebookアカウントでログイン
            </v-btn>
            <v-btn
                block
                class="color-google text-capitalize mb-3"
                @click="googleLogin"
            >
                <span
                    class="color-google__icon v-icon notranslate v-icon--left mdi theme--light"
                >
                    <svg
                        enable-background="new 0 0 46 46"
                        viewBox="0 0 46 46"
                        xmlns="http://www.w3.org/2000/svg"
                    >
                        <g transform="translate(14 14)">
                            <g clip-rule="evenodd" fill-rule="evenodd">
                                <path
                                    d="m31.1 9.5c0-1.6-.1-3.2-.4-4.7h-21.7v8.9h12.4c-.5 2.9-2.2 5.3-4.6 6.9v5.8h7.4c4.4-4 6.9-9.9 6.9-16.9z"
                                    fill="#4285f4"
                                />
                                <path
                                    d="m9 32c6.2 0 11.4-2.1 15.2-5.6l-7.4-5.8c-2.1 1.4-4.7 2.2-7.8 2.2-6 0-11.1-4-12.9-9.5h-7.7v6c3.8 7.5 11.6 12.7 20.6 12.7z"
                                    fill="#34a853"
                                />
                                <path
                                    d="m-3.9 13.4c-.4-1.4-.7-2.9-.7-4.4s.3-3 .7-4.4v-6h-7.7c-1.5 3.2-2.4 6.7-2.4 10.4s.9 7.2 2.4 10.3z"
                                    fill="#fbbc05"
                                />
                                <path
                                    d="m9-4.9c3.4 0 6.4 1.2 8.8 3.4l6.6-6.5c-4-3.7-9.2-6-15.4-6-9 0-16.8 5.2-20.6 12.7l7.7 6c1.8-5.5 6.9-9.6 12.9-9.6z"
                                    fill="#ea4335"
                                />
                            </g>
                            <path d="m-14-14h46v46h-46z" fill="none" />
                        </g>
                    </svg>
                </span>
                Googleアカウントでログイン
            </v-btn>
        </v-col>
    </v-row>
</template>

<style lang="scss" scoped>
@mixin social_button($brand-color: #999,$text-color: #fff){
    background-color: $brand-color !important;
    border-color: $brand-color;
    color: $text-color;

    @at-root {
        #{&}__icon {
            position: absolute;
            left: 0;
        }
    }
}

.color-twitter {
    @include social_button(#1da1f2);
}
.color-facebook {
    @include social_button(#3b5998);
}
.color-google {
    @include social_button(#fff, #757575);
    @at-root {
        #{&}__icon > svg {
            position: absolute;
        }
    }
}
</style>

<script>
import { auth } from '~/plugins/firebase.js'

export default {
    methods: {
        twitterLogin: function(err) {
            this.$store
                .dispatch('signInWithTwitter')
                .then(() => {
                    this.$router.push({
                        name: 'index'
                    })
                })
                .catch((err) => {
                    this.$parent.socialLoginErrorMsg =
                        '現在Twitterでのログインは使用できません。後ほどお試しください。'
                })
        },
        facebookLogin: function(err) {
            this.$store
                .dispatch('signInWithFacebook')
                .then(() => {
                    this.$router.push({
                        name: 'index'
                    })
                })
                .catch((err) => {
                    this.$parent.socialLoginErrorMsg =
                        '現在Facebookでのログインは使用できません。後ほどお試しください。'
                })
        },
        googleLogin: function(err) {
            this.$store
                .dispatch('signInWithGoogle')
                .then(() => {
                    this.$router.push({
                        name: 'index'
                    })
                })
                .catch((err) => {
                    this.$parent.socialLoginErrorMsg =
                        '現在Googleでのログインは使用できません。後ほどお試しください。'
                })
        }
    }
}
</script>

パスワード強度チェッカーの実装(おまけ)

サンプルのユーザー登録画面のパスワード入力欄にzxcvbnを使用したパスワードの強度チェッカーを実装しています。

zxcvbnは、入力されたパスワードの強度を0〜4のスコアとして返してくれます。

Vuejs側では、入力されたパスワードを取得してzxcvbnから返ってきたスコアに応じて入力欄のボーダー色が赤から緑に変わるようにしてあげることで、よくユーザー登録画面のパスワード欄で見るアレを簡単に実装することができます。

実際のコードは、register.vueを参考にしてください。

zxcvbnのインストール npmの場合

npm install zxcvbn --save

zxcvbnのインストール yarnの場合

yarn add zxcvbn save

NUXTアプリケーションの立ち上げ

フロントUIの準備が整ったら以下のコマンドを実行してNUXTアプリケーションを立ち上げてみましょう。メールアドレスでのユーザー登録や、ログイン、ソーシャルログインが動作するのが確認できるかと思います。

問題なく立ち上がれば、http://localhost:3000/ でブラウザからアクセスできるかと思います。

npmの場合

npm run dev

yarnの場合

yarn run dev

チュートリアルのファイル一式ダウンロード

Githubにチュートリアルで使用したファイル一式をアップしていますので、気軽にダウンロードしていじってみてください。

joey-i / nuxt-firebase-auth-sample

vuex-persistedstateを使用するシーン

前述の通り、多数のFirebase + NUXTのチュートリアル系のサイトでユーザーの認証状態の管理(vuexの永続化)のためにvuex-persistedstateを使用すると記載されているところが多いですが、実際のとこりユーザーの認証状態の永続化目的では不要です。

ただ、このままだとユーザーがログアウトをしない限りログインし続けている状態となってしまいます。これだと、提供するサービスの内容によっては好ましくない状況かと思います。

そこで、vuex-persistedstateを使用してlocalStorageではなく、オプションでsessionStorageを指定して使用することによって、ログアウトしていなくてもブラウザを閉じれば、sessionStorageが空になるので、sessionStorage内の値の有無をチェックして無ければ、ログアウトさせる処理を組み込めば、ログインしっぱなし状態を避けることができるようになります。

ただし、この場合でもFirebase AuthのメソッドのsignInWithRedirectでは、リダイレクトしているので、値が返ってくるタイミングなどの差異によるものかと思いますが、うまく動作しませんでした。signInWithPopupを使用する必要があります。

vuex-persistedstateのインストール npmの場合

npm install vuex-persistedstate --save

vuex-persistedstateのインストール yarnの場合

yarn add vuex-persistedstate save

vuex-persistedstateでのsessionStorageの指定方法

以下のコードをpersistedstate.jsなどとしてplugins配下に保存して、nuxt.config.jsのpluginsセクションに読み込むように追記してあげれば、使用することができます。

import createPersistedState from “vuex-persistedstate”

export default ({store, isHMR}) => {
    // In case of HMR, mutation occurs before nuxReady, so previously saved state
    // gets replaced with original state received from server. So, we’ve to skip HMR.
    // Also nuxtReady event fires for HMR as well, which results multiple registration of
    // vuex-persistedstate plugin
    if (isHMR) return

    if (process.client) {
        window.onNuxtReady((nuxt) => {
            createPersistedState({
                storage: window.sessionStorage
            })(store) // vuex plugins can be connected to store, even after creation
        })
    }
}

参考にさせていただいたサイト

Nuxt+Firebaseでログイン機能を実装する - muutoの日記

「middlewareでlocalStorageのvuexがとれない」と言う同じ症状を書かれていたので、そのキーワードあたりを参考にさせていただきました。

Building User Accounts with Nuxt, Vuex, and Firebase

英語のサイトですが、midldlewareあたりの実装など、一番参考にさせていただいたかもしれません。ありがとうございます。

Related Articles
For Every type of your business

開発やコンサルティングのご相談は、
お問い合わせフォームからお気軽に。