この記事ではFirebase Authenticationがどういったものなのか、インストールの仕方などの説明は省略し、
Vue.jsのプロジェクトにFirebase Authenticationでどのように認証機能を実装するのかを説明していきます。
今回はフレームワークとしてVue.jsを採用していますが、どのフレームワークを使ったとしてもSPAであれば実装方法はさほど変わらないと思います。
またHTTPリクエストにはaxiosを使用します。
SSRと違いSPAに認証機能を入れる場合、ログイン機能を実装するだけではだめで、下記の機能をクライアントで実装する必要があります。
- ページ遷移の制御
- ログインしていなければログインページにリダイレクトさせるなど
- Authorizationヘッダー
- リクエストヘッダーにID TokenつけてHTTPリクエストを行う
- ID Tokenの期限が切れたときに更新処理を行う
各機能の実装方法を説明していく前に、毎回FirebaseSDKのメソッドを呼んでいては冗長なので、
必要なメソッドをまとめたクラスを用意します。
またID Tokenをキャッシュさせるためにシングルトンにしています。
これは、HTTPリクエストのたびに getIdTokenResult を呼びたくないからです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
import * as firebase from "firebase/app"; import "firebase/auth"; const firebaseConfig = { ... }; class Firebase { constructor() { firebase.initializeApp(firebaseConfig); this.token = ''; } async login(email, password) { return await firebase.auth().signInWithEmailAndPassword(email, password); } async signOut() { return await firebase.auth().signOut(); } async getToken() { return new Promise((resolve, reject) => { if (firebase.auth().currentUser && this.token) { resolve(this.token); } else { this.onAuth(async(user) => { if (user) { const token = await this.refreshToken(); resolve(token); } else { reject(); } }); } }) } // 現在ログインしているユーザーを取得する async onAuth() { return new Promise((resolve, reject) => { firebase.auth().onAuthStateChanged(async(user) => { if (user) { resolve(user); } else { reject(); } }); }) } async refreshToken() { return new Promise(resolve => { // ID トークンを取得 firebase.auth().currentUser.getIdTokenResult() .then((idTokenResult) => { this.token = idTokenResult.token; resolve(this.token); }) .catch((error) => { console.log(error); }); }) } } export default new Firebase(); |
クラスが用意できたところで三点の実装に移っていきます。
目次
ページ遷移の制御
Vue.jsでSPAならVue Routerを使っていると思うので、ナビゲーションガードを使って制御します。
https://router.vuejs.org/ja/guide/advanced/navigation-guards.html
これは遷移前、遷移後のナビゲーションをフックすることで、任意の処理を入れた後で遷移やリダイレクトの処理を実装することができる機能です。
今回は beforeEach を使って遷移前の処理を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
router.beforeEach((to, from, next) => { if (to.path === '/login') { next(); } else { Firebase.onAuth() .then(() => { next(); }) .catch(() => { next({path: '/login'}) }) } }); |
まずは遷移先のパスをチェックして、ログインページならそのまま遷移、
それ以外であれば onAuthStateChanged を呼んでログインユーザーが存在しているか確認します。
存在していればそのまま遷移させ、存在していなければログインページにリダイレクトさせます。
今回はパスをチェックしていますが、メタフィールドを使って認証が必要なページを細かく設定する方法もあります。
https://router.vuejs.org/ja/guide/advanced/meta.html
Authorizationヘッダー
axiosでAuthorizationヘッダーをつけてリクエストすることは下記のように簡単に実装できます。
1 2 3 4 5 |
axios.get('https://api.hoge/fuga', { headers: { Authorization: `Bearer ${token}`, } }) |
ただこれを毎回書くことは面倒なのでaxios-middlewareを使って共通の処理としてまとめます。
axios-middlewareを使うことでaxiosのHTTPリクエストを簡単にフックすることができます。
リクエスト前に処理を挟みたいので、 onRequest でリクエストパラメーターにAuthorizationヘッダーを付けてあげます。
これはPromiseを返すことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import axios from 'axios'; import { Service } from 'axios-middleware'; import Firebase from '@/library/firebase'; const service = new Service(axios); service.register({ onRequest(config) { return Firebase.getToken() .then(token => { config.headers.Authorization = `Bearer ${token}`; return config; }) .catch(e => e); } }); |
ID Tokenの更新
ここまでで問題が無いように思えますが、Firebase Authenticationのトークンは一時間で期限が切れてしまうため(またトークンをキャッシュしているため)
ページを開いたまま一時間が経ってしまうと、HTTPリクエストがエラーになってしまいます。
なので
- リクエストエラーのフック
- トークンの更新
- HTTPリクエストのリトライ処理
を実装しなければいけません。
リクエストエラーのフックはaxios-middlewareの onResponseError を使います。
トークンの更新はFirebaseSDKの getIdTokenResult を呼ぶことで更新処理が行われます。
そしてリトライ処理ですが、 onResponseError で受け取ったリクエストパラメーターを使い回すことで実現できます。
具体的な実装はこうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
onResponseError(err) { if (err.response.status === 401 && err.config && !err.config.hasRetriedRequest) { const http = axios.create(); return Firebase.refreshToken() .then((token) => http({ ...err.config, hasRetriedRequest: true, headers: { ...err.config.headers, Authorization: `Bearer ${token}` } })) .catch((error) => { console.log('Refresh login error: ', error); throw error; }); } throw err; } |
まずは認証系エラーなのかを判断するためにステータスコードを確認します。
これはサーバーサイドとの実装の兼ね合いもあるので、必ずしもこうであるとは限りません。
次にリトライ回数に制限を設けるために、 hasRetriedRequest フラグを確認します。
初回リクエストでは false を、リトライリクエストでは true を設定することで、
二回目以降はリトライ処理を行いません。
axios.create でHTTPリクエスト用のインタスタンスを生成し、 getIdTokenResult を呼んでトークンの更新処理を行ってから、
受け取ったリクエストパラメーターを再度設定し二度目のリクエストを行います。
まとめ
リトライ処理は割と面倒くさい印象がありますが、axios-middlewareを使うことで簡単に実装することができました。
今回は基本的な認証機能の紹介になりましたが、カスタムクレームとVueRouterのメタフィールドを使って細かな権限の制御などを行うことも可能です。
https://firebase.google.com/docs/auth/admin/custom-claims?hl=ja