この記事は、「Firebase Advent Calendar 2019」の14日目の記事です。
FirebaseとFirebaseUIとVue.jsで複数プロバイダログイン機能を構築してみました!
事前準備
- Firebaseの環境構築
アプリの作成とログインプロバイダの設定が必要になります。Twitter等のSNSは事前に、SNS側でのAPIキー等の取得が必要です。
Firebase #001 – 環境構築
JavaScript による Twitter を使用した認証
- Vue.jsの環境準備
下記2つのリンクの環境を利用して今回拡張していますが、新規で作成しても問題ありません。
Vue.js #002 – Bootstrap4をインストール
Try #025 – Firebaseと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
Menu.vue
<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
- 参考文献
FirebaseUI
Firebase
Vue.js