参考講座
超Vue.js 2 完全パック (Vue Router, Vuex含む)
以下の記事の続きになります。
【Vue.js】Firebaseとaxiosを使ってサーバーにhttp通信をする方法
この記事を読むメリット
以下のメリットが得られます。
- tokenの役割について理解できる。
- Firebaseを用いた認証機能を理解できる。
- Vue.jsを用いてユーザー登録、ユーザー認証(ログイン)機能の実装方法を理解できる。
- vue-routerを用いて、フロントエンドでルーティングする方法を理解できる。
- ログアウト機能の実装方法を理解できる。
token: トークンについて

ユーザー名とパスワードを指定しないとサーバーからtokenが返却されない。
tokenには、いつ作られたのか、ユーザーは誰なのか、token有効期限はいつまでなのか、といった情報が含まれている。この符号化された情報(token)をローカルに一時保存して、それをサーバーへ返す。
これによりtokenが一致していることから、ログインができるようになる。
※firebase tokenの場合、有効期限は1時間
ログインフォームの実装
vue-router のインストール
routerの設定
src/router.js
Routerの記載と、viewのパスを3つ(Comments, Login, Register)を記述します。
import Vue from 'vue'; import Router from 'vue-router'; import Comments from './views/Comments.vue'; import Login from './views/Login.vue'; import Register from './views/Register.vue'; Vue.use(Router); export default new Router({ mode:'history', routes:[ { path:'/', component:Comments }, { path:'/login', component:Login }, { path:'/register', component:Register }, ] });
src/main.js
routerを追加します。
import Vue from 'vue';
import App from './App.vue';
import axios from 'axios';
import router from './router';
Vue.config.productionTip = false;
axios.defaults.baseURL = "https://firestore.googleapis.com/v1/projects/[PROJECT_ID]/databases/(default)/documents";
new Vue({
router,
render: h => h(App),
}).$mount('#app')
[PROJECT_ID]の部分は、後述するFirebaseのプロジェクトの設定で確認するプロジェクトIDに変更する。
src/App.vue
<template>
<div id="app">
<header>
<router-link to="/" class="header-item">掲示板</router-link>
<router-link to="/login" class="header-item">ログイン</router-link>
<router-link to="/register" class="header-item">登録</router-link>
</header>
<router-view></router-view>
</div>
</template>
<style scoped>
.header-item{
padding: 10px;
}
</style>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
画面(掲示板、ログイン、登録)の作成
vueファイルを3つ作成します。
src/views/Comments.vue
<template>
<div>
<h3>掲示板に投稿する</h3>
<label for="name">ニックネーム</label>
<input id="name" type="text" v-model="name">
<br><br>
<label for="comment">コメント</label>
<textarea id="comment" v-model="comment"></textarea>
<br><br>
<button @click="createComment">コメントをサーバーに送る</button>
<h2>掲示板</h2>
<div v-for="post in posts" :key="post.name">
<br>
<div>名前:{{post.fields.name.stringValue}}</div>
<div>コメント:{{post.fields.comment.stringValue}}</div>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
data(){
return{
name:"",
comment:"",
posts:[]
};
},
created(){
axios.get(
"/comments"
).then(response=>{
this.posts = response.data.documents;
console.log(response.data.documents);
});
},
methods:{
createComment(){
axios.post("/comments",
{
fields:{
name: {
stringValue: this.name
},
comment:{
stringValue: this.comment
}
}
}).then(response => {
console.log(response);
}).catch(error => {
console.log(error);
});
this.name="";
this.comment="";
}
}
}
</script>
src/views/Login.vue
<template>
<div>
<h2>ログイン</h2>
<label for="email">Email</label>
<input id="email" type="email" v-model="email">
<br><br>
<label for="password">Password</label>
<input id="password" type="password" v-model="password">
<br><br>
<button @click="login">ログイン</button>
</div>
</template>
<script>
export default {
data(){
return{
email:"",
password:""
}
},
methods:{
login(){
}
}
}
</script>
src/views/Register.vue
一旦はLogin.vueとほぼ同じ感じ(コピペ)で作成します。
<template>
<div>
<h2>登録</h2>
<label for="email">Email</label>
<input id="email" type="email" v-model="email">
<br><br>
<label for="password">Password</label>
<input id="password" type="password" v-model="password">
<br><br>
<button @click="register">登録</button>
</div>
</template>
<script>
export default {
data(){
return{
email:"",
password:""
}
},
methods:{
register(){
}
}
}
</script>
この段階で以下のように表示されます。
掲示板

ログイン

登録

掲示板については、前回記事で名前とコメントを入力して、ボタンを押すと名前とコメントが画面に表示されます。
ログインと登録は、この段階で画面表示のみとなります。
Firebase Authenticationの利用
Authentication→Sign-in method→メール/パスワードをクリック

有効にする。

利用方法について
https://firebase.google.com/docs/reference/rest/auth
エンドポイント
このURLをコピーする。
src/axios-auth.js
ファイルを以下のように作成する。
コピーしたURLをbaseURLの部分に張り付ける。
import axios from "axios";
const instance = axios.create({
baseURL:'https://identitytoolkit.googleapis.com/v1'
});
export default instance;
accounts:signUp?key=[API_KEY]
の部分を削除する。
API_KEYの確認
プロジェクトを設定をクリック

ウェブAPIキーをコピーする。

ユーザー登録機能の実装
src/views/Register.vue
axios.postの引数にaccounts:signUp?key=[API_KEY]
と記載して、[API_KEY]をウェブAPIキーに変更する。
<script>
import axios from '../axios-auth';
export default {
data(){
return{
email:'',
password:''
}
},
methods:{
register(){
axios.post(
'/accounts:signUp?key=[API_KEY]',
{
email:this.email,
password:this.password,
returnSecureToken:true
}).then(response=>{
console.log(response);
});
}
}
}
</script>
第二引数にはリクエストボディペイロードを参考に
https://firebase.google.com/docs/reference/rest/auth
メール、パスワード、トークンを記載。

firebase 側でユーザーを確認

ユーザー情報が登録されていることを確認。
ログイン認証の実装
firebase側で確認したAPIキーを元に、下のURLの[API_KEY]部分を変更しておきます。
Register.vueとほぼ同じなので、<script>タグの部分をコピーして、Login.vueに張り付けます。
メソッド名をlogin、postの第一引数のURLパスを
/accounts:signInWithPassword?key=[API_KEY]
に変更します。
<script>
import axios from '../axios-auth';
export default {
data(){
return{
email:'',
password:''
}
},
methods:{
login(){
axios.post(
'/accounts:signInWithPassword?key=[API_KEY]',
{
email:this.email,
password:this.password,
returnSecureToken:true
}).then(response=>{
console.log(response);
});
}
}
}
</script>
この状態でログイン画面側を確認してみます。

ステータス200でうまくいってそうなことがわかります。
firebaseの設定
firebase上のcloud Firestoreにあるルールで
のようにすることで、ログインしていない(tokenがない)と、ボードに投稿内容が表示されないようにすることができます。
投稿データの受け渡し
投稿した内容を、掲示板上に表示するために、データを受け渡す必要があります。
データの受け渡しにはvuexを利用します。
vuex をインストール
src/store/index.js
Login.vueのactions(login, register)の内容をこちらに記載。
import Vue from 'vue'
import Vuex from 'vuex'
import axios from '../axios-auth';
Vue.use(Vuex);
export default new Vuex.Store({
state:{
idToken: null
},
getters: {
idToken: state => state.idToken
},
mutations:{
updateToken(state, idToken){
state.idToken = idToken;
}
},
actions: {
login({commit}, authData){
axios.post(
'/accounts:signInWithPassword?key=[API_KEY]',
{
email:authData.email,
password:authData.password,
returnSecureToken:true
}).then(response=>{
commit('updateToken', response.data.idToken);
console.log(response);
});
},
register({commit}, authData){
axios.post(
'/aaccounts:signUp?key=[API_KEY]',
{
email:authData.email,
password:authData.password,
returnSecureToken:true
}).then(response=>{
commit('updateToken', response.data.idToken);
console.log(response);
});
}
});
loginにはaccounts:signInWithPassword?
registerにはaccounts:signUp?
を使う
src/views/Login.vue
<template>
<div>
<h2>ログイン</h2>
<label for="email">Email</label>
<input id="email" type="email" v-model="email">
<br><br>
<label for="password">Password</label>
<input id="password" type="password" v-model="password">
<br><br>
<button @click="login">ログイン</button>
</div>
</template>
<script>
export default {
data(){
return{
email:'',
password:''
}
},
methods:{
login(){
this.$store.dispatch('login', {
email: this.email,
password: this.password
});
this.email='';
this.password='';
}
}
}
</script>
this.$store.dispatchでemailとpasswordをstore側に渡す。
tokenをヘッダーにつけてリクエストを送る。
src/views/Comments.vue
<script>
import axios from "axios";
export default {
data(){
return{
name:"",
comment:"",
posts:[]
};
},
computed: {
idToken(){
return this.$store.getters.idToken;
}
},
created(){
axios.get(
"/comments", {
headers: {
Authorization: `Bearer ${this.idToken}`
}
}
).then(response=>{
this.posts = response.data.documents;
console.log(response.data.documents);
});
},
methods:{
createComment(){
axios.post("/comments",
{
fields:{
name: {
stringValue: this.name
},
comment:{
stringValue: this.comment
}
}
},{
headers: {
Authorization: `Bearer ${this.idToken}`
}
}).then(response => {
console.log(response);
}).catch(error => {
console.log(error);
});
this.name="";
this.comment="";
}
}
}
</script>
computedでidTokenを取得する。
axios.getの第二引数、axios.postの第三引数にheadersを追加する。
これにより、ログイン後、コメントが掲示板に表示されるようになる。
また投稿した内容も表示されるようになる。
ログイン認証による掲示板表示
src/router.js
ログインしていないと、掲示板は表示できない、遷移できないようにする。
import store from './store';
Vue.use(Router);
export default new Router({
mode:'history',
routes:[
{
path:'/',
component:Comments,
beforeEnter(to, from, next){
if (store.getters.idToken) {
next();
} else {
next('/login');
}
}
},
{
path:'/login',
component:Login,
beforeEnter(to, from, next){
if (store.getters.idToken) {
next('/');
} else {
next();
}
}
},
{
path:'/register',
component:Register,
beforeEnter(to, from, next){
if (store.getters.idToken) {
next('/');
} else {
next();
}
}
},
]
});
beforeEnterを追加する。
既にログインしている場合、掲示板を表示する。
ログインしていない場合、/login に飛ばす。
ログインと登録をクリックしたとき、ログインしていれば、掲示板へ飛ばす。
ログインした後に、掲示板の方へ飛ぶようにする。
src/store/index.js
actions: {
login({commit}, authData){
axios.post(
'/accounts:signInWithPassword?key=[API_KEY]',
{
email:authData.email,
password:authData.password,
returnSecureToken:true
}).then(response=>{
commit('updateToken', response.data.idToken);
console.log(response);
router.push('/');
});
},
register({commit}, authData){
axios.post(
'/accounts:signUp?key=[API_KEY]',
{
email:authData.email,
password:authData.password,
returnSecureToken:true
}).then(response=>{
commit('updateToken', response.data.idToken);
console.log(response);
router.push('/');
});
}
}
loginとregisterのaxios.post().thenに
router.push(‘/’);
を追加する。
App.vueにisAuthenticatedを加えて、ヘッダーの表示制御をする。
<script> export default { computed: { isAuthenticated(){ return this.$store.getters.idToken !== null; } } }; </script>
ログインしていれば、”掲示板”を表示
<template v-if="isAuthenticated"> <router-link to="/" class="header-item">掲示板</router-link> </template>
ログインしていない場合、”ログイン”と”登録”を表示
<template v-if="!isAuthenticated"> <router-link to="/login" class="header-item">ログイン</router-link> <router-link to="/register" class="header-item">登録</router-link> </template>
tokenの自動更新
※tokenを更新する場合
UXとのトレードオフ(セキュリティを重視するか、利便性を重視するか)
1時間でtokenの有効期限が切れるため、1時間おきにidTokenを更新する処理を追加する。
src/store/index.js
actions内にsetAuthData関数を作り、localStorage.setItemでidToken、expiryTimeMs、refreshTokenをローカルストレージにセットする。
actions: {
async autoLogin({commit, dispatch}){
const idToken = localStorage.getItem('idToken');
if (!idToken) return;
const now = new Date();
const expiryTimeMs = localStorage.getItem('expiryTimeMs');
const isExpired = now.getTime() >= expiryTimeMs;
const refreshToken = localStorage.getItem('refreshToken');
if (isExpired) {
await dispatch('refreshIdToken', refreshToken);
} else{
const expiresInMs = expiryTimeMs - now.getTime();
setTimeout(()=>{
dispatch('refreshIdToken', refreshToken);
}, expiresInMs);
commit('updateIdToken', idToken);
}
},
login({dispatch}, authData){
axios.post(
'/accounts:signInWithPassword?key=[API_KEY]',
{
email:authData.email,
password:authData.password,
returnSecureToken:true
}).then(response=>{
dispatch('setAuthData', {
idToken: response.data.idToken,
expiresIn: response.data.expiresIn,
refreshToken: response.data.refreshToken
});
router.push('/');
});
},
async refreshIdToken({dispatch}, refreshToken){
await axiosRefresh.post('/token?key=[API_KEY]',
{
grant_type: 'refresh_token',
refresh_token: refreshToken,
}).then(response=>{
dispatch('setAuthData', {
idToken: response.data.idToken,
expiresIn: response.data.expiresIn,
refreshToken: response.data.refreshToken
});
});
},
register({dispatch}, authData){
axios.post(
'/accounts:signUp?key=[API_KEY]',
{
email:authData.email,
password:authData.password,
returnSecureToken:true
}).then(response=>{
dispatch('setAuthData', {
idToken: response.data.idToken,
expiresIn: response.data.expiresIn,
refreshToken: response.data.refreshToken
});
console.log(response);
router.push('/');
});
},
setAuthData({commit, dispatch}, authData){
const now = new Date();
const expiryTimeMs = now.getTime() + authData.expiresIn * 1000;
commit('updateIdToken', authData.idToken);
localStorage.setItem('idToken', authData.idToken);
localStorage.setItem('expiryTimeMs', expiryTimeMs);
localStorage.setItem('refreshToken', authData.refreshToken);
setTimeout(() =>{
dispatch('refreshIdToken', authData.refresh_token);
}, authData.expiresIn * 1000);
}
}
ログアウト機能の実装
src/App.vue
ログアウトボタンを用意します。
<template>
<div id="app">
<header>
<template v-if="isAuthenticated">
<router-link to="/" class="header-item">掲示板</router-link>
<span class="header-item" @click="logout">ログアウト</span>
</template>
<template v-if="!isAuthenticated">
<router-link to="/login" class="header-item">ログイン</router-link>
<router-link to="/register" class="header-item">登録</router-link>
</template>
</header>
<router-view></router-view>
</div>
</template>
<script>
export default {
computed: {
isAuthenticated(){
return this.$store.getters.idToken !== null;
}
},
methods:{
logout(){
this.$store.dispatch('logout');
}
}
};
</script>
methodsにlogout関数を用意して、storeにdispatchします。
src/store/index.js
logout({commit}){
commit('updateIdToken', null);
localStorage.removeItem('idToken');
localStorage.removeItem('expiryTimeMs');
localStorage.removeItem('refreshToken');
router.replace('/login');
},
actions:の中のloginの下あたりに上記のlogoutを追加します。
ログアウトボタンが押されたときに、local storageに保存されているidToken, epireyTimeMs, refreshTokenの値を取り除いて、/loginにrouter.replaceでナビゲーションさせます。

ログアウトを押すと、ログイン画面が表示されて、Local Storageに保存されていたkeyとvalueがなくなったことがわかります。

まとめ
- tokenを用いてユーザーの認証をする。
- Firebaseを用いた認証機能を簡単にかつ無料で利用することができる。
- Vue.jsおよびaxiosを用いてフロントエンド側のユーザー認証機能を構築できる。
- vue-routerを用いて、フロントエンドでルーティングできる。
- local storageをクリアすることでログアウト機能を実装できる。
今回のログイン認証機能のバックエンドはfirebase側に任せていますが、自前でバックエンド側も用意しようとする場合、axiosのパスの部分をcontrollerや(laravel利用想定)や別のパスに変えてあげればいけそうかと思います。
Githubにコードを公開しています。ご利用の際は、firebaseの設定部分だけご自身で用意したものに値を変更して下さい。
以上になります。お疲れ様でした!

