「Vue.js の主要な機能をざっくりとつかってみたときのメモ」や「Vue.js の主要な機能をざっくりとつかってみたときのメモ(Firebase編)」のつづきです。。
前回までで、FirebaseのFirestoreにTodoリストをCRUDしました。今回はFirebaseのOAuthの認証・認可機能を使ってみます。
OAuthってのは、いわゆる「Googleアカウントでログイン」などのリンクを押すと、Googleのサイトのポップアップが出てきて、このアカウントをつかってログインしていい?って聞かれるヤツです。
本来は、認可サーバであるGoogleが「Firebaseをつかった(WEB)アプリに、あなたのGoogleアカウント情報を読ませてもいいかな?」って、Googleアカウントのオーナ(ユーザ)に許可つまり認可させるという「認可のための」仕組みなのですが、その際にポップアップでユーザを認証するため、結果として「Firebaseをつかった(WEB)アプリにGoogleアカウントでログインする(ユーザ情報が連携される)という処理シーケンスとなります。
参考:Authleteを使った認可サーバの構築と、OAuthクライアント(Webアプリケーション)からの疎通
そのまえに bootstrap-vue いれさせて
そのまえに、前回から、今回の記事作成までの最中にbootstrap-vue を導入しているので、そのインストールについて。
インストールとコードへの反映
$ npm install bootstrap-vue --save
インストールはこれだけであとは main.js に以下を追加します。
import BootstrapVue from "bootstrap-vue"
import "bootstrap/dist/css/bootstrap.min.css"
import "bootstrap-vue/dist/bootstrap-vue.css"
Vue.use(BootstrapVue)
これでvue.jsでbootstrap-vueが利用可能になりました。。Header.vueなどにログインリンクなどをつけたり適度に装飾してますが、コードはあとで出てくるので、ココでは割愛します :-)
Firebaseの認証をやってみる
Google認証を有効にする
Firebaseのコンソールから、自分が作成したプロジェクトを選択し、左のメニュー部の「Authentication」を選択。
「ログイン方法」を選択すると、下記のようにログイン方法を選択する画面になります。今回はGoogleをつかうので、プロバイダのGoogleを「有効」にしておきましょう。
OAuthの認可サーバを使用するときって、認可サーバ側からWEBアプリに対して「client_id/client_secret」を払い出してもらい、それらをパラメタに載せることでWEBアプリ自体の認証を行うのですが、Google もFirebaseもGoogle だからですかね、この辺はあらかじめ設定済みになってるようです。
承認済みドメインを追加する
この認証機能を利用出来るドメインを指定します。いまはlocalhostで起動しているので設定不要ですが、外部に公開するサーバのURLが http://client.example.com:8080 などだった場合、 client.example.com を設定追加しておきましょう。
コレを行わないと、あとで出てくる firebase.auth().signInWithPopup
関数がexception
となるみたいですね。
ライブラリのインストール
Firebaseのライブラリは前回インストール済みなのですが、ログインしているユーザ情報やログイン状態をjsやvue間で共有するために、オブジェクトの状態を管理するフレームワークVuexを導入します。
Vuexは、 Vuex.Store というオブジェクトに対して、
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: {},
loginStatus: false
},
mutations: {
user (state, user) { state.user = user },
loginStatus (state, loginStatus) { state.loginStatus = loginStatus }
},
plugins: [
createPersistedState({storage: window.sessionStorage, key: 'vuex-todo-examples'})
]
})
というインスタンスを保持する領域 user
,loginStatus
を作成しておき、jsやvueからは
<script>
import firebase from 'firebase'
export default {
name: 'Header',
computed: {
loginStatus () {
return this.$store.state.loginStatus // vuexのインスタンスを参照
},
user () {
return this.$store.state.user // vuexのインスタンスを参照
}
},
...
}
</script>
とやることで参照ができます。
保存しているインスタンスの更新については、直接プロパティを操作するのではなく
<script>
import firebase from 'firebase'
export default {
name: 'Login',
... 割愛
methods: {
loginWithGoogle () {
const provider = new firebase.auth.GoogleAuthProvider()
firebase
.auth()
.signInWithPopup(provider)
.then(result => {
this.$store.commit('user', result.user) // インスタンスの更新
this.$store.commit('loginStatus', true) // インスタンスの更新
this.$router.push(
this.$route.query.redirect ? this.$route.query.redirect : '/'
)
})
.catch(function (error) {
const errorCode = error.code
const errorMessage = error.message
alert(errorMessage)
})
}
}
}
</script>
このように$store.commit('メソッド名','値')
という仕様でVuex.Store
にmutationsで定義していたメソッドを呼びだすことで、データを更新します。
このVuexをつかえばアプリケーション全体で状態が管理出来るので、Googleログインが成功した際やユーザ情報が更新されたとき、そのインスタンスを保存しておき、各画面ではそのインスタンスのあるなしで、ログイン済みかどうかを判定することができそうです。
ということでVuexのインストール
npm install --save vuex vuex-persistedstate
vuexはライブラリそのもの、vuex-persistedstate がそのデータをLocalStorageやSessionStorageに保存するためのライブラリです。
ソース追加・修正内容
さてソースについて。多いので一覧をつけました。
- src/main.js で、Firebaseのユーザ情報の更新を検知
- src/store.js: Vuexの追加
- components/Header.vue: ヘッダにログアウトリンクを追加
- router/index.js にログイン画面のルーティング追加
- components/Login.vue: /loginで呼ばれる画面を追加
順番に見ていきましょう。
src/main.js で、Firebaseのユーザ情報の更新を検知
ユーザがログイン中かの判定は、Googleアカウントログイン後や、そのユーザ情報が更新された際、そのユーザ情報をvuexに保存しておいて、そのあるなしをチェックすればよいという話でした。
なのでmain.jsで先ほどのstore.jsをimportし「Firebaseの認証機能を経由してユーザ情報が更新されたときに呼ばれるコールバック」で、vuexのユーザ情報とログイン状態を更新することにしました。
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from '@/store' // 追加
import firebase from 'firebase'
import firebaseConfig from '@/firebaseConfig'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)
firebase.initializeApp(firebaseConfig)
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION) // 追加
Vue.config.productionTip = false
// 追加
firebase.auth().onAuthStateChanged(function (user) {
// ユーザ情報が変更されたら呼ばれる
if (user) {
// User is signed in.
store.commit('user', user)
store.commit('loginStatus', true)
} else {
store.commit('user', {})
store.commit('loginStatus', false)
}
})
// ここまで
// router.beforeEach((to, from, next) => {
// あとで追加する
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
src/store.js: Vuexの追加
ユーザ情報とログイン状態を保持するためのvuexを作成しました。先のソースとおなじものです。
// さっき載せたのとおなじ
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: {},
loginStatus: false
},
mutations: {
user (state, user) { state.user = user },
loginStatus (state, loginStatus) { state.loginStatus = loginStatus }
},
plugins: [
createPersistedState({storage: window.sessionStorage, key: 'vuex-todo-examples'})
]
})
components/Header.vue: ヘッダにログアウトリンクを追加
つづいてHeader.vueです。共通ヘッダにしていた箇所に(まだログインも実装してないんですがorz)ログアウトのリンクを追加します。
<template>
に色々書いてますが、bootstrapのGUIの記述がほとんどです。ログアウトリンクの表示可否をvuexのloginStatus
で制御しています。
また、ログアウトリンクをクリックしたときに呼ばれる logout()
メソッドも追加しています。こちらもfirebase.auth().signOut()
を呼び出している程度で、とてもシンプルです。
<template>
<b-navbar toggleable="md" type="dark" variant="info">
<b-navbar-toggle target="nav_collapse"></b-navbar-toggle>
<b-navbar-brand href="#">ToDo管理</b-navbar-brand>
<b-collapse is-nav id="nav_collapse">
<!-- Right aligned nav items -->
<b-navbar-nav class="ml-auto" v-if="loginStatus">
<b-nav-item-dropdown right>
<!-- Using button-content slot -->
<template slot="button-content">
<em>
<span>{{user.displayName}}</span>
</em>
</template>
<b-dropdown-item @click="logout()">{{user.displayName}}さんを Signout</b-dropdown-item>
</b-nav-item-dropdown>
</b-navbar-nav>
</b-collapse>
</b-navbar>
</template>
<script>
import firebase from 'firebase'
export default {
name: 'Header',
computed: {
loginStatus () {
return this.$store.state.loginStatus
},
user () {
return this.$store.state.user
}
},
methods: {
logout () {
firebase.auth().signOut()
// のちに画面遷移処理を追加する。
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
router/index.js にログイン画面のルーティング追加
Routerについては、/login でログイン画面を表示させるためのルーティングを追加しています。
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Login from '@/components/Login'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
// ↓追加
{
path: '/login',
component: Login
}
]
})
components/Login.vue: /loginで呼ばれる画面を追加
さて /login で呼び出される実際のログイン画面です。
http://localhost:8080/#/login で表示されるログイン画面となります。
<template>
<b-container>
<div class="form-signin">
<button type="button" class="google-button" @click="loginWithGoogle">
<span class="google-button__icon">
<svg viewBox="0 0 366 372" xmlns="http://www.w3.org/2000/svg"><path d="M125.9 10.2c40.2-13.9 85.3-13.6 125.3 1.1 22.2 8.2 42.5 21 59.9 37.1-5.8 6.3-12.1 12.2-18.1 18.3l-34.2 34.2c-11.3-10.8-25.1-19-40.1-23.6-17.6-5.3-36.6-6.1-54.6-2.2-21 4.5-40.5 15.5-55.6 30.9-12.2 12.3-21.4 27.5-27 43.9-20.3-15.8-40.6-31.5-61-47.3 21.5-43 60.1-76.9 105.4-92.4z" id="Shape" fill="#EA4335"/><path d="M20.6 102.4c20.3 15.8 40.6 31.5 61 47.3-8 23.3-8 49.2 0 72.4-20.3 15.8-40.6 31.6-60.9 47.3C1.9 232.7-3.8 189.6 4.4 149.2c3.3-16.2 8.7-32 16.2-46.8z" id="Shape" fill="#FBBC05"/><path d="M361.7 151.1c5.8 32.7 4.5 66.8-4.7 98.8-8.5 29.3-24.6 56.5-47.1 77.2l-59.1-45.9c19.5-13.1 33.3-34.3 37.2-57.5H186.6c.1-24.2.1-48.4.1-72.6h175z" id="Shape" fill="#4285F4"/><path d="M81.4 222.2c7.8 22.9 22.8 43.2 42.6 57.1 12.4 8.7 26.6 14.9 41.4 17.9 14.6 3 29.7 2.6 44.4.1 14.6-2.6 28.7-7.9 41-16.2l59.1 45.9c-21.3 19.7-48 33.1-76.2 39.6-31.2 7.1-64.2 7.3-95.2-1-24.6-6.5-47.7-18.2-67.6-34.1-20.9-16.6-38.3-38-50.4-62 20.3-15.7 40.6-31.5 60.9-47.3z" fill="#34A853"/></svg>
</span>
<span class="google-button__text">Sign in with Google</span>
</button>
</div>
</b-container>
</template>
<script>
import firebase from "firebase";
// import 'firebaseui/dist/firebaseui.css'
export default {
name: "Login",
methods: {
loginWithGoogle() {
const provider = new firebase.auth.GoogleAuthProvider();
firebase
.auth()
.signInWithPopup(provider)
.then(result => {
this.$store.commit("user", result.user);
this.$store.commit("loginStatus", true);
this.$router.push(
this.$route.query.redirect ? this.$route.query.redirect : "/"
);
})
.catch(function(error) {
const errorCode = error.code;
const errorMessage = error.message;
alert(errorMessage);
});
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
html,
body {
height: 100%;
}
body {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
/* Shared */
.loginBtn {
box-sizing: border-box;
position: relative;
/* width: 13em; - apply for fixed size */
margin: 0.2em;
padding: 0 15px 0 46px;
border: none;
text-align: left;
line-height: 34px;
white-space: nowrap;
border-radius: 0.2em;
font-size: 16px;
color: #fff;
cursor: pointer;
}
.loginBtn:before {
content: "";
box-sizing: border-box;
position: absolute;
top: 0;
left: 0;
width: 34px;
height: 100%;
}
.loginBtn:focus {
outline: none;
}
.loginBtn:active {
box-shadow: inset 0 0 0 32px rgba(0, 0, 0, 0.1);
}
.google-button {
height: 40px;
border-width: 0;
background: white;
color: #737373;
border-radius: 5px;
white-space: nowrap;
box-shadow: 1px 1px 0px 1px rgba(0, 0, 0, 0.05);
transition-property: background-color, box-shadow;
transition-duration: 150ms;
transition-timing-function: ease-in-out;
padding: 0;
box-shadow: 1px 4px 5px 1px rgba(0, 0, 0, 0.1);
}
.google-button:hover {
cursor: pointer;
}
.google-button:active {
box-shadow: 3px 6px 7px 3px rgba(0, 0, 0, 0.1);
transition-duration: 10ms;
}
.google-button__icon {
display: inline-block;
vertical-align: middle;
margin: 8px 0 8px 8px;
width: 18px;
height: 18px;
box-sizing: border-box;
}
.google-button__icon--plus {
width: 27px;
}
.google-button__text {
display: inline-block;
vertical-align: middle;
padding: 0 24px;
font-size: 14px;
font-weight: bold;
font-family: "Roboto", arial, sans-serif;
}
</style>
npm run dev
して表示してみると、こんな感じ。
コードが長くてちょっとアレですが、<template>
と<style>
、<script>
それぞれみてみましょう。
まずtemplate部とstyle部いわゆる画面は、Googleログインのリンクボタンを追加しているだけです。styleはまあ長いですがボタンに適用しているcssですね。
つぎにボタンのクリックで呼ばれるメソッド loginWithGoogle
(script部)ですが、
methods: {
loginWithGoogle(){
const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(......)
...
}
}
まずココまででポップアップが表示され、そのWindowsにGoogleがログイン画面を表示してくれます。
ポープアップ画面でGoogleへのログインとユーザの認可オペが完了すると、ポップアップは自動で閉じられ、then
に記述した下記のコールバックが呼び出されます。
result => {
this.$store.commit('user', result.user)
this.$store.commit('loginStatus', true)
this.$router.push(
this.$route.query.redirect ? this.$route.query.redirect : '/'
// あとで詳細説明
)
}
このコールバック処理ではvuexにあるuser/loginStatusを
- コールバックに渡ってくるユーザ情報(
result.user
) - ログインステータスを
true
で更新し、'/' へ画面遷移します。 '/'はルーティングでいままでのToDo画面が定義されているので、結果としてGoogle認証が完了するとToDo画面が表示されることになります。
ちなみにToDo画面であるHelloWorld.vue については、全体を
<main v-if="$store.state.loginStatus" class="container">
で囲うことで、ログインステータスが true
のときのみコンテンツを表示するようにしています。
<template>
<main v-if="$store.state.loginStatus" class="container">
<h1>
My Todo Task
<span class="info">({{remainingTask.length}}/{{todos.length}})</span>
<span class="info" style="cursor:pointer" @click="checkAll()" v-if="!isAllChecked()">すべてチェック/はずす</span>
<span class="info" style="cursor:pointer" @click="unCheckAll()" v-if="isAllChecked()">すべてチェック/はずす</span>
<b-button size="sm" variant="secondary" @click="deleteEndTask">完了タスクの削除</b-button>
</h1>
<ul>
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox" v-model="todo.isDone" @click="toggle(todo.id)">
<span v-bind:class="{done: todo.isDone}">{{todo.name}}</span>
<span @click="deleteTask(todo.id)" class="xButton">[x]</span>
</li>
</ul>
<form @submit.prevent="addTask">
<input type="text" v-model="newTask" placeholder="タスクを入力">
<b-button type="submit" variant="primary" style="margin:4px">追加</b-button>
</form>
</main>
</template>
いま時点のソース
ここまでのソースは
https://github.com/masatomix/todo-examples/tree/for_qiita_auth_001
へコミット済みです。
一応このタグから構築する手順を示しておきます。
$ git clone --branch for_qiita_auth_001 https://github.com/masatomix/todo-examples.git
$ cd todo-examples/
$ npm install
src/firebaseConfig.js を自分の設定に書き換え
$ npm run dev
これで、http://localhost:8080/#/login にブラウザでアクセスできるとおもいます。
いったん整理
さてここまでで、Googleアカウントでログインして、ToDoアプリケーションを使用するという基本的なところができあがりました。しかしながらまだ
- ヘッダ右上のドロップダウンの「ログアウト」を選んだら、ログイン画面に遷移させたい(今はログアウトさせるだけ)
- 画面に応じて、ログインが必要・必要でない、を制御したい
- 画面にアクセスしたときに、ログイン済みでない場合はログイン画面を表示し、認証したら該当画面へ遷移するようにしたい
- Firestore へのデータアクセスを、認証されたユーザのみに制限したい
などを対応したいので、つづけてやっていきます。
ヘッダ右上のドロップダウンの「ログアウト」を選んだら、ログイン画面に遷移させたい
ヘッダのログアウトを選択すると、logout()
が呼ばれますが、
methods: {
logout () {
firebase.auth().signOut()
// のちに画面遷移処理を追加する。
}
}
にログアウト後のコールバックを追加します。
methods: {
logout () {
firebase.auth().signOut()
.then(() => {
this.$router.push('/')
window.location.reload() // 保持してた不要な情報を一度クリア(vuexはきえない)
})
.catch(function (error) {
const errorCode = error.code
const errorMessage = error.message
alert(errorMessage)
})
}
}
this.$router.push('/')
だとログイン画面ではなくてToDo画面に行こうとするんですが、このあとの機能追加で **「ログイン済み状態じゃなかったらログイン画面に飛ばす」**処理を入れるので、結果ログイン画面に遷移します。
ちなみに firebase.auth().signOut()
のコールバックに入った時点でユーザ情報は変更され、その結果main.jsに追加した
firebase.auth().onAuthStateChanged(function (user) {// ユーザ情報が変更されたら呼ばれる
if (user) {
// User is signed in.
store.commit('user', user)
store.commit('loginStatus', true)
} else {
store.commit('user', {})
store.commit('loginStatus', false)
}
})
がうごきだして、vuex上の user
,loginStatus
はそれぞれ{}
とfalse
で更新されます。
画面に応じて、ログインが必要・必要でない、を制御したい
さて、認証されていない場合はログイン画面に飛ばしたいのですが、ログイン画面が認証が必要だと無限ループするので、画面によってログインの必要可否を制御したいですね。
ということで、画面ごとの定義を router/index.js に追加します。
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Login from '@/components/Login'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/login',
component: Login,
meta: {
isPublic: true // このプロパティ追加
}
}
]
})
isPublicがtrueの場合は、認証状態をチェックしないで画面を表示します。デフォルトはfalseなので、ログイン画面以外は認証が必要という設定になりました。
この次の項の画面遷移の制御で、上記の定義を参照する機能を追加します。
画面にアクセスしたときに、ログイン済みでない場合はログイン画面を表示し、認証したら該当画面へ遷移する
さあもうすこしです。
src/main.js にナビゲーションガードを追加する
公式のナビゲーションガード に説明がありますが、ナビゲーションガード機構を使うことで、画面の遷移をフックして、状態をチェックするなどの処理を入れ込むことが出来ます。
こんな感じに、main.jsでナビゲーションガードを追加します。
router.beforeEach((to, from, next) => {
const currentUser = store.state.user
if (currentUser.uid) {
if (to.path === '/login') {
firebase.auth().signOut().then(() => next())
}
}
if (to.matched.some(record => record.meta.isPublic)) {
// alert('isPublic = true '+ to.path)
next()
} else {
// alert('isPublic = false '+ to.path)
if (currentUser.uid) {
next()
} else {
next({
path: '/login',
query: {
redirect: to.path
}
})
}
}
})
つまり、URLが変更されるときに変更前のURL情報(from)と変更後のそれ(to)が渡ってくるので、
- vuex からユーザ情報が取れたときは、toがログイン画面(/login)へのアクセスだったら、いちどログアウトさせてユーザ情報をクリアしてから、ログイン画面へ遷移(next())させる
- 遷移先(to)のURLについて、isPublicがtrueの場合は、そのまま遷移(next())させる
- isPublicがfalseの場合は、
- vuex からユーザ情報(
store.state.user.uid
)が取れたときはそのまま遷移(next()) - vuex からユーザ情報(
store.state.user.uid
)が取れない場合は、ログイン画面へ遷移させる(※))
- vuex からユーザ情報(
最後の処理(※)は
next({
path: '/login',
query: {
redirect: to.path
}
})
の処理のことです。
/login に遷移する際 redirect というクエリパラメタに to.pathという変数をセットしていますが、たとえば /ui001 に遷移しようとしてナビゲーションガードによってログイン画面に遷移させられたとき、遷移後のログイン画面(/login)のURLが
http://localhost:8080/#/login?redirect=%2Fui001
とクエリパラメタが後ろにつくようにしています。このように redirect にto.pathを渡しているのは、ログインが成功した際に本来行きたかった画面のURL(/ui001ですね) の情報が必要だからです。
Login.vue でのログイン成功後の遷移先
さて、渡されたredirectパラメタですが、Login.vue でログイン成功後の画面遷移 はこのようになっていました。
this.$router.push(
this.$route.query.redirect ? this.$route.query.redirect : '/'
)
コレはつまり、redirectパラメタがある場合はそこのURLへ遷移、パラメタがない場合は '/' へ遷移(デフォルト値) するってことですね。
以上で、認証が必要な画面に行こうとした場合、ナビゲーションガードによって認証されているかがチェックされ、必要に応じてログイン画面を出した後、本来行きたい画面に遷移する流れを実装することが出来ました。
うーん、疲れましたね。。。
Firestore へのデータアクセスを、認証されたユーザのみに制限したい
さあFirebaseでの認証ができたので、せっかくなのでFirestoreへのアクセスを、認証されたユーザのみに出来たらよりセキュアですね。
そのための制御はFirebase側の画面にあります。
https://console.firebase.google.com から自分のプロジェクトに移動し、Database >> ルール に遷移します
ココですが、ワタクシまえに権限エラーに対応するため、下記の通りだれでもOK!ってしていたんですが、
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
// allow read, write: if request.auth.uid != null;
}
}
}
これを
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
//allow read, write;
allow read, write: if request.auth.uid != null;
}
}
}
にして「公開」ボタンを押せばOKです。ほどなくして反映されるようで、認証状態でないFirestoreへのアクセスにはエラーが返るようになりました。
ナビゲーションガードなどを入れたソース
最終形のソースは
https://github.com/masatomix/todo-examples/tree/for_qiita_auth_002
へコミット済みです。
下記手順でビルドできます。
$ git clone --branch for_qiita_auth_002 https://github.com/masatomix/todo-examples.git
$ cd todo-examples/
$ npm install
src/firebaseConfig.js を自分の設定に書き換え
$ npm run dev
これで、http://localhost:8080/#/ にブラウザでアクセスできるとおもいます。
ログイン画面 '/login' とToDo画面 '/' の他に、'/ui001','/ui002'という画面を追加でコミットしています。/ui001だけ認証が必要な画面にしてあるので、
http://localhost:8080/#/ui001 などに直接アクセスしてみて、ログイン画面が表示され、ログインするとちゃんと行きたかった画面に遷移できること、などを確認してみてください。
まとめ
Firebase認証をつかえば、OAuthをつかったGoogle認証機能を簡単に実装することが出来ましたね。
ザックリまとめると、、ログイン状態はVuexで管理し、ログイン時やユーザ情報の変更に従ってVuexの情報を更新します。また、画面遷移時にログイン状態をチェックするナビゲーションガードを導入することで、必要な時だけログイン画面を挟むことができるようになりました。最後にFirestoreへのアクセスを、Firebaseログインしているユーザに制限することで、データを保護することも出来ました。
以上です。おつかれさまでした。
関連リンク
- Vue vuexでfirebaseのログイン保持
- 面倒なログイン機能の実装はFirebase Authenticationに丸投げしよう 概要が整理されています
- Firebase Authentication を使って得られた知見まとめ - トークンの仕様や注意点など 戻り電文のJWTの仕様が丁寧に説明されています。
- Firebase Auth のユーザ認証機能を自前のデータベースと連携する Firebaseを認証だけにつかう場合。
- ブラウザでログインしたFirebaseのユーザー情報をサーバー側で取得する その2
- Authenticating a Vue JS Application With Firebase UI Firebase UI っていう、画面まで作ってくれちゃうライブラリをつかう場合。
- Vue.jsにおけるFirebaseの主要な機能の取扱い
- Vue Routerのナビゲーションガードによるアクセス制限を試した&コードを読み解いた
- 【備忘録】【Vue.js】routeを使ってログインしてないと見れないように設定する方法
- Firebase / Firestore を使って簡単な Chat を作ってみる。(JS, Vue)
- Vue.js 2.0 でログイン (vue-router で認証が必要な URL を定義)
- Vue.js の主要な機能をざっくりとつかってみたときのメモ
- Vue.js の主要な機能をざっくりとつかってみたときのメモ(Firebase編)
- Vue.js の主要な機能をざっくりとつかってみたときのメモ(Firebase認証・認可編) 本記事