どーも、ぐるたか@guru_takaです。
今までNuxtとFirebaseのログイン情報をonAuthStateChanged
で監視していました。
しかし、リロードすると、ログイン情報を取得するまでにラグが発生。そこでSSRする前に、サーバー側でログイン情報を取得して、ラグなしでレンダリングする方法を探しました。
すると、公式でService Worker によるセッション管理がする方法があったので、実際に試しました。
すると、動画のように、ログイン情報に関わる描画のラグがなくなって超ハッピー!
今回はその方法を紹介していきます。
NuxtにServiceWorkerを導入してセッション管理して、Firebaseのユーザー情報を取得してみた
リロードしてもラグなし!
快適なSSRにまた一歩近づいた pic.twitter.com/Lb0Utjtsd1
— ぐるたか (@guru_taka) January 13, 2020
目次
大まかな仕組み
Service Workerを利用して、サーバーへリクエストする時、ユーザーのIDトークンをリクエストヘッダーに追加します。
そして、Nuxtが最初に起動するタイミングで、サーバー側にて発火するnuxtServerInit
で、認証状態をチェック!
具体的には、リクエストにヘッダーに追加されたIDトークンを取得し、firebase-admin
で有効性を確認します。
後はIDトークンをデコードして、認証中のユーザー情報を復元するイメージです!
大まかな手順
STEP.1:Nuxt PWAのモジュールをインストール
まずは、PWAサポートのモジュールをインストール!
$ npm i @nuxtjs/pwa
その後、nuxt.config.js
に追記しましょう。
{
modules: [
'@nuxtjs/pwa',
],
}
npm create nuxt-app
でPWAサポートをすでにインストールしていれば、STEP1をスキップしてOKです!STEP2:Service Workerの作成
続いて、ervice Workerを作成していきます。
ここは公式からコピペすればOK!
Nuxt+Firebaseでセッション管理: PWA(Service Worker)編に、日本語にてわかりやすいコメントが記載されているので、引用させて頂きます。
本当にありがたい、感謝!!!
以下のコードでやっていることは
です。
// firebaseを初期化
firebase.initializeApp({
apiKey: /* API_KEY */,
authDomain: /* AUTH_DOMAIN */,
databaseURL: /* DATABASE_URL */,
projectId: /* PROJECT_ID */,
storageBucket: /* STORAGE_BUCKET */,
messagingSenderId: /* MESSAGING_SENDER_ID */,
appId: /* APP_ID */,
measurementId: /* AUTH_DOMAIN */
});
// onAuthStateChanged()で現在のuserからidTokenを取得
const getIdToken = () => {
return new Promise((resolve, reject) => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
unsubscribe();
if (user) {
user.getIdToken().then(
idToken => resolve(idToken),
error => resolve(null)
);
} else {
resolve(null);
}
});
});
};
// URLからルートのURLを取得する処理
const getOriginFromUrl = url => {
// https://stackoverflow.com/questions/1420881/how-to-extract-base-url-from-a-string-in-javascript
const pathArray = url.split("/");
const protocol = pathArray[0];
const host = pathArray[2];
return protocol + "//" + host;
};
/**
* Service Workderのライフサイクルでfetchしたときの処理
*/
self.addEventListener("fetch", event => {
// リクエストをラップして、ヘッダにFirebase AuthのIdTokenを追加する処理
const requestProcessor = idToken => {
let req = event.request;
// URLを取得して、httpsもしくはlocalhostかなどをチェック
if (self.location.origin == getOriginFromUrl(event.request.url) &&
(self.location.protocol == "https:" || self.location.hostname == "localhost") &&
idToken
) {
// ヘッダ情報をクローンする
const headers = new Headers();
for (let entry of req.headers.entries()) {
headers.append(entry[0], entry[1]);
}
// クローンしたヘッダにFirebase AuthのIdTokenを追加
headers.append("Authorization", "Bearer " + idToken);
try {
req = new Request(req.url, {
method: req.method,
headers: headers,
mode: "same-origin",
credentials: req.credentials,
cache: req.cache,
redirect: req.redirect,
referrer: req.referrer,
body: req.body,
bodyUsed: req.bodyUsed,
context: req.context
});
} catch (e) {
console.error(e);
}
}
return fetch(req);
};
// 上の関数を使って、全リクエストでIdTokenの取得し、Firebase AuthのIdTokenを追加ようにする
event.respondWith(getIdToken().then(requestProcessor, requestProcessor));
});
/**
* Service Workderのライフサイクルでactivateしたときの処理
*/
self.addEventListener("activate", event => {
event.waitUntil(clients.claim());
});
STEP3:nuxt.config.jsの設定を追加
nuxt.config.js
にworkboxの設定を追加します。workboxはPWAを良い感じにしてくれるライブラリです。
workbox: {
//firebaseのauth周りが使えうように、CDNでfirebase-appとfirebase-authを追加
importScripts: [
"https://www.gstatic.com/firebasejs/7.6.1/firebase-app.js",
"https://www.gstatic.com/firebasejs/7.6.1/firebase-auth.js",
"sw-firebase-auth.js"
],
dev: process.env.MODE != "production",
},
dev: process.env.MODE != "production",
を追加することで、dev開発でもsw.js
が生成されます!
上記を生成しないと、sw.js
はプロダクションモードのでしか生成されないので、注意してください。
STEP4:firebase-adminのインスタンス初期化用のファイルを準備
リクエストにヘッダーに追加されたIDトークンの有効性をサーバー側で確認するためにFirebase Admin SDK
を準備します。
また、ユーザー認証後にサーバー側でfirestore
なども使えるようになります!
$ npm install firebase-admin --save
let admin
if (process.server) {
admin = require('firebase-admin')
if (!admin.apps.length) {
const serviceAccount = require('./path/serviceAccountKey.json')
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: '自分のdatabaseURL'
})
}
}
export default admin
秘密キーのダウンロード方法は、こちらの記事にスクショ付きでまとめていますので、参考にしてみて下さい!
【Nuxt】NuxtServerInit内でfirestoreからデータを取得する方法
STEP5:nuxtServerInitで、認証状態をチェック
後は、nuxtServerInit
でリクエストヘッダーにあるトークンIDをデコードしていきます。
コメント付きのソースを以下に記します!
async nuxtServerInit(
{ state, commit, dispatch },
{ app, error, route, redirect, req }
) {
// requestのAuthorizationからIDトークンを取得
const authorizationHeader = req.headers.authorization || ''
//配列化
const components = authorizationHeader.split(' ')
//配列の2番目の要素がトークンID
const token = components.length > 1 ? components[1] : ''
if (!token) return
// firebase-adminの初期化
const admin = require('~/utils/firebaseAdmin').default
if (!admin) return
// IDトークンの検証&デコード: 有効期限などをFirebaseでチェック
const decodedClaims = await admin.auth().verifyIdToken(token)
// 検証結果からuser情報を取得
//decodedClaims:デコードした属性情報
const user = decodedClaims
// TODO:user.user_idがuid
// uid使った処理を以下に書く
},
これで、サーバー側で認証チェックできるので、認証周りのレンダリングやリダイレクト処理などもSSRできてUXがより良くなります!
認証後にfirestoreを使いたい方は以下の記事を参考にしてみて下さい!
【Nuxt】NuxtServerInit内でfirestoreからデータを取得する方法
注意:ハードリロード時は、Sevice Workerは動かない
ハードリロードした時はSeviceWorkerが動かないので、サーバー側でユーザー認証できないので注意しましょう。
コメントを残す