dayjournal memo

Total 974 articles!!

Try #032 – FirebaseとFirebaseUIとVue.jsで複数プロバイダログイン機能を構築してみた

Yasunori Kirimoto's avatar

画像


画像




画像




この記事は、「Firebase Advent Calendar 2019」の14日目の記事です。




FirebaseとFirebaseUIとVue.jsで複数プロバイダログイン機能を構築してみました!



事前準備



全体の構成はできるだけシンプルに。

バックエンド - ユーザー管理

  • Firebase

フロントエンド - ログイン機能

  • Vue.js
  • FirebaseUI


バックエンド


まずは、バックエンドを構築していきます。基本的にはコンソール設定のみで構築終わります!


準備でアプリが作成されている場合は、ユーザー認証方法の設定を追加します。今回はメールとTwitterを対象として設定してみます。

画像


これでバックエンドの構築は完了になります。



フロントエンド


次に、フロントエンドを構築していきます。


実行環境

node v12.7.0
npm v6.10.0

画像


はじめに、FirebaseUIをインストールします。(Firebase本体はインストール済を想定)

npm install firebaseui

画像



FirebaseUIの日本語版もインストールします。

npm install firebaseui-ja

画像



あとは、実際に複数プロバイダログインのコードを記述していきます。今回は下記赤線の6ファイルについて、追加・修正をしていきます。


全体構成

画像


package.json

{
  "name": "firebase_prj",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "bootstrap-vue": "^2.0.0-rc.19",
    "core-js": "^2.6.5",
    "firebase": "^6.3.5",
    "firebaseui": "^4.3.0",
    "firebaseui-ja": "^1.0.0",
    "vue": "^2.6.10",
    "vue-router": "^3.0.3",
    "vuex": "^3.0.1"
  },
  "devDependencies": {
    "@babel/polyfill": "^7.4.4",
    "@vue/cli-plugin-babel": "^3.8.0",
    "@vue/cli-plugin-eslint": "^3.8.0",
    "@vue/cli-service": "^3.8.0",
    "babel-eslint": "^10.0.1",
    "bootstrap": "^4.3.1",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0",
    "mutationobserver-shim": "^0.3.3",
    "node-sass": "^4.12.0",
    "popper.js": "^1.15.0",
    "portal-vue": "^2.1.4",
    "sass-loader": "^7.1.0",
    "vue-cli-plugin-bootstrap-vue": "^0.4.0",
    "vue-template-compiler": "^2.6.10"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "rules": {},
    "parserOptions": {
      "parser": "babel-eslint"
    }
  },
  "postcss": {
    "plugins": {
      "autoprefixer": {}
    }
  },
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ]
}


/src


main.js

import '@babel/polyfill'
import 'mutationobserver-shim'
import Vue from 'vue'

import App from './App.vue'
import router from './router'
import store from './store'

// bootstrap-vue読み込み
import './plugins/bootstrap-vue'

// Firebase読み込み
import firebase from 'firebase'
// FirebaseUI読み込み
import firebaseui from 'firebaseui-ja'
import 'firebaseui-ja/dist/firebaseui.css'

Vue.config.productionTip = false

// Firebase設定
let firebaseConfig = {
    apiKey: "xxxxxxxxxx",
    authDomain: "sample-1908.firebaseapp.com",
    databaseURL: "https://sample-1908.firebaseio.com",
    projectId: "sample-1908",
    storageBucket: "",
    messagingSenderId: "xxxxxxxxxx",
    appId: "xxxxxxxxxx"
};
firebase.initializeApp(firebaseConfig);

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')


router.js

import Vue from 'vue'
import Router from 'vue-router'

// 各view読み込み
import Main from './views/Main.vue'
import MyPage from './views/MyPage.vue'
import Login from './views/Login.vue'

// Firebase読み込み
import firebase from 'firebase'

Vue.use(Router)

let router = new Router({
    // #を無くすためにはサーバーの設定も必要
    mode: 'history',
    routes: [
        {
            // ログインページ
            path: '/login',
            name: 'Login',
            component: Login
        },
        {
            // メインページ
            path: '/main',
            name: 'main',
            component: Main,
            meta: { requiresAuth: true }
        },
        {
            // マイページ
            path: '/mypage',
            name: 'myPage',
            component: MyPage,
            meta: { requiresAuth: true }
        },
        {
            // リダイレクト
            path: '/',
            redirect: '/login'
        }
    ]
});

// ナビゲーションの前に実行
router.beforeEach((to, from, next) => {
    // ログインの有無判断
    const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
    if (requiresAuth) {
        firebase.auth().onAuthStateChanged(function (user) {
            if (user) {
                // ログイン時は各ページに移動
                next()
            } else {
                // 未ログイン時はログイン画面にリダイレクト
                next({
                    path: '/login'
                })
            }
        })
    } else {
        next()
    }
});

export default router


src/views


Login.vue

<template>
    <div>
        <h2>Login</h2>
        <div id='firebaseui-auth-container'></div>
    </div>
</template>

<script>
    // Firebase読み込み
    import firebase from 'firebase'
    // FirebaseUI読み込み
    import firebaseui from 'firebaseui-ja'
    import 'firebaseui-ja/dist/firebaseui.css'

    export default {
        name: 'Login',
        data() {
            return {
            }
        },
        mounted() {
            // thisを格納
            const root = this;
            // 認証設定
            const uiConfig = {
                callbacks: {
                    signInSuccessWithAuthResult: function(authResult) {
                        // 認証種類判定
                        if (authResult.additionalUserInfo.providerId === 'twitter.com') {
                            return true;
                        } else {
                            // 確認メールの有無
                            const mailFlag = authResult.user.emailVerified;
                            if (mailFlag === false) {
                                // 確認メール未時に確認メール送信
                                firebase.auth().currentUser.sendEmailVerification()
                                    .then(function() {
                                        alert('登録メールを送信しました。ご確認ください。');
                                        // URLリロード
                                        root.$router.go()
                                    })
                                    .catch(function(error) {
                                    });
                            } else {
                                // 確認メール済時にメイン画面へ移動
                                return true;
                            }
                        }
                    }
                },
                // 認証時に同一ウィンドウで表示
                signInFlow: 'redirect',
                // ログイン後リダイレクト先
                signInSuccessUrl: '/main',
                // 各認証
                signInOptions: [
                    // twitter認証
                    firebase.auth.TwitterAuthProvider.PROVIDER_ID,
                    // メール認証
                    firebase.auth.EmailAuthProvider.PROVIDER_ID
                ],
                // 利用規約へリンク
                tosUrl: 'https://day-journal.com/memo',
                // プライバシーポリシーリンク
                privacyPolicyUrl: 'https://day-journal.com/memo'
            };

            // 認証UI表示
            const ui = new firebaseui.auth.AuthUI(firebase.auth());
            ui.start('#firebaseui-auth-container', uiConfig);

        }
    };
</script>

<style scoped>
    h2 {
        margin-top: 40px;
        margin-bottom: 40px;
        text-align: center;
    }
</style>


Main.vue

<template>
    <div class='main'>
        <!--メニュー-->
        <Menu></Menu>

        <h2>ログインしました!</h2>
    </div>
</template>

<script>
    import Menu from '@/components/Menu.vue'

    export default {
        name: 'Main',
        components: {
            Menu
        }
    }
</script>

<style scoped>
    h2 {
        margin-top: 80px;
        text-align: center;
    }
</style>


MyPage.vue

<template>
    <div class='myPage'>
        <!--メニュー-->
        <Menu></Menu>

        <b-container>
            <b-row>
                <b-col cols='12' offset='5' class='my-5'>
                    <h4>ユーザー情報</h4>
                </b-col>

                <b-col cols='12' offset='5' class='mb-5'>
                    <div v-if='isLogin'>
                        <div>
                            name: {{loginUser.displayName}}
                        </div>
                        <div>
                            img: <img :src='loginUser.photoURL'>
                        </div>
                    </div>
                </b-col>

                <b-col cols='12' offset='5' class='mb-5'>
                    <!--削除ボタン-->
                    <button
                        @click='deleteUser'
                        class='btn btn-primary mt-3'
                    >ユーザー削除</button>
                </b-col>
            </b-row>
        </b-container>
    </div>
</template>

<script>
    // Firebase読み込み
    import firebase from 'firebase'
    import Menu from '@/components/Menu.vue'

    export default {
        name: 'Main',
        components: {
            Menu
        },
        data() {
            return {
                isLogin: false,
                loginUser: null
            };
        },
        mounted() {
            // ユーザー情報取得
            firebase.auth().onAuthStateChanged(user => {
                this.isLogin = true;
                this.loginUser = user;
            });
        },
        methods: {
            deleteUser: function () {
                // ユーザー削除処理
                firebase.auth().currentUser.delete()
                    .then(function(res) {
                        console.log("currentUser.delete", res);
                    })
                    .catch(function(error) {
                        console.log(error);
                    });
            }
        }
    }
</script>

<style scoped>

</style>


src/components


<template>
    <div class='menu'>
        <b-navbar toggleable='lg' type='light' variant='info' class='shadow-sm'>
            <b-navbar-toggle target='nav-collapse'></b-navbar-toggle>
            <b-collapse id='nav-collapse' is-nav>
                <b-navbar-nav class='ml-auto'>
                    <!--ユーザー表示-->
                    <b-nav-item-dropdown text='設定' right>
                        <!--マイページへ-->
                        <b-dropdown-item class='dropdown-link-color' href='mypage'> マイページ </b-dropdown-item>
                        <!--ログイン画面へ-->
                        <b-dropdown-item class='dropdown-link-color' href='#' @click="logout()" > ログアウト </b-dropdown-item>
                    </b-nav-item-dropdown>
                </b-navbar-nav>
            </b-collapse>
        </b-navbar>
    </div>
</template>

<script>
    // Firebase読み込み
    import firebase from 'firebase'

    export default {
        name: 'Menu',
        data() {
            return {
            }
        },
        methods: {
            logout: function () {
                // ログアウト処理
                firebase.auth().signOut();
            }
        }
    }
</script>

<style scoped>
    .dropdown-link-color {
        color: #212529;
    }
    .dropdown-link-color:hover{
        text-decoration: none;
    }
    .link-color {
        color: rgba(255,255,255,.5);
    }
    .link-color:hover{
        text-decoration: none;
        color: rgba(255,255,255,.8);
    }
</style>


簡易ローカルサーバーで確認

npm run serve


ローカルサーバーを立ち上げて、複数プロバイダログインしてみます。



メールログイン流れ

メールアドレス登録 → メール認証

画像

画像

画像

画像

画像


メールログイン

画像

画像

画像

画像

画像

画像



Twitterログイン流れ

Twitterログイン

画像

画像

画像

画像

画像




FirebaseとFirebaseUIとVue.jsを利用することで、手軽に複数プロバイダログイン機能の構築ができました!


以前紹介した「Try #025 – FirebaseとVue.jsでログイン機能を構築してみた」とFirebaseUIを組み合わせると、UIも含んだ複数プロバイダログイン機能を実現できるので非常に便利です。

FirebaseUIはすごく便利なんですが、記事が意外と少なかったので書いてみました!



Vue.jsについて、他にも記事を書いています。よろしければぜひ。
tags - Vue.js



book

Q&A