tag:crieit.net,2005:https://crieit.net/tags/Firebase-Auth/feed
「Firebase-Auth」の記事 - Crieit
Crieitでタグ「Firebase-Auth」に投稿された最近の記事
2019-12-29T19:07:50+09:00
https://crieit.net/tags/Firebase-Auth/feed
tag:crieit.net,2005:PublicArticle/15656
2019-12-29T19:07:03+09:00
2019-12-29T19:07:50+09:00
https://crieit.net/posts/Nuxt-Firebase-PWA-Service-Worker
Nuxt+Firebaseでセッション管理: PWA(Service Worker)編
<p>FirebaseとSSRなNuxt.jsでアプリを作っていて、<br />
クライアント側で認証チェックするとFirebaseの初期化などでラグが...<br />
サーバ側で認証情報とかを取得してもう少しなんとかできないかなと。</p>
<p>まだベータっぽい?けど、公式の以下の内容を試してみたときの備忘録。</p>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/web/service-worker-sessions?hl=ja">Service Worker によるセッション管理 | Firebase</a></li>
</ul>
<h3 id="よく出てくる言葉"><a href="#%E3%82%88%E3%81%8F%E5%87%BA%E3%81%A6%E3%81%8F%E3%82%8B%E8%A8%80%E8%91%89">よく出てくる言葉</a></h3>
<p>単語はよく聞くけど、ちゃんと見てなかったので、ざっくりとしたまとめ</p>
<ul>
<li>PWA: ネイティブアプリみたいなUXを提供するWebアプリ
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/web/progressive-web-apps/">Progressive Web Apps | Google Developers</a></li>
</ul></li>
<li>Service Worker: PWAを実現するための基盤技術。独自のライフサイクルを持ってる
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/web/fundamentals/primers/service-workers?hl=ja">Service Worker の紹介 | Web Fundamentals | Google Developers</a></li>
</ul></li>
<li>Workbox: PWAでよく使うコード(ボイラープレート)やベストプラクティスを提供するライブラリ
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/web/tools/workbox/">Workbox | Google Developers</a></li>
</ul></li>
<li>Nuxt PWA: NuxtでPWAするときに使うプラグイン。workboxを使ったService Worker(sw.js)とかを生成してくれる
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://pwa.nuxtjs.org/">⚡ Nuxt PWA</a></li>
</ul></li>
</ul>
<h3 id="Nuxt PWAを使ってみる"><a href="#Nuxt+PWA%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">Nuxt PWAを使ってみる</a></h3>
<h4 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h4>
<pre><code class="shell">$ npm install @nuxtjs/pwa
</code></pre>
<h4 id="設定"><a href="#%E8%A8%AD%E5%AE%9A">設定</a></h4>
<p>設定は簡単。modulesに<code>@nuxtjs/pwa</code>を追加するだけ。</p>
<pre><code class="typescript">// nuxt.config.ts
import { Configuration } from "@nuxt/types";
const config: Configuration = {
// ...略
modules: [
// ... 略
"@nuxtjs/pwa",
],
// ...略
};
export default config;
</code></pre>
<h3 id="Firebase Authと組み合わせる"><a href="#Firebase+Auth%E3%81%A8%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E3%82%8B">Firebase Authと組み合わせる</a></h3>
<p>このあたりを見つつ、Firebase Authの情報を扱えるようにする。<br />
- <a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/web/service-worker-sessions?hl=ja">Service Worker によるセッション管理 | Firebase</a><br />
- <a target="_blank" rel="nofollow noopener" href="https://qiita.com/daishinkawa/items/915d918aba6bf7849b21#auth%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF">Firebase (Hosting × Functions) × Nuxt.js (universal) で ユーザ認証のベストプラクティスを探る旅 その2 - Qiita</a></p>
<p>流れ的には、以下の通り。</p>
<ol>
<li>Firebase Auth用のService Workerの作成して、リクエストにIDトークンを付与するように変更</li>
<li>作成したService Workerを使うよう、nuxt.config.tsに設定を追加</li>
<li></li>
</ol>
<h4 id="Firebase Auth用のService Workerの作成"><a href="#Firebase+Auth%E7%94%A8%E3%81%AEService+Worker%E3%81%AE%E4%BD%9C%E6%88%90">Firebase Auth用のService Workerの作成</a></h4>
<p>長めだけど、ほぼ<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/web/service-worker-sessions?hl=ja">公式ドキュメント</a>のまま。</p>
<pre><code class="javascript">// ~/static/sw-firebase-auth.js
// 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());
});
</code></pre>
<p>やっていることは、以下のような感じ。</p>
<ol>
<li>リクエストするときに、</li>
<li>現在のユーザからIDトークンを取得して</li>
<li>IDトークンをリクエストヘッダーに追加する</li>
</ol>
<p>ヘッダーにIDトークンが付与されているので、<br />
サーバ側でそれを見て、認証済みかをチェックする。</p>
<h3 id="nuxt.config.tsへの取り込み"><a href="#nuxt.config.ts%E3%81%B8%E3%81%AE%E5%8F%96%E3%82%8A%E8%BE%BC%E3%81%BF">nuxt.config.tsへの取り込み</a></h3>
<p>作成したサービスワーカを使うように、nuxt.config.tsに設定を追加する。<br />
ドキュメントだと<a target="_blank" rel="nofollow noopener" href="https://pwa.nuxtjs.org/modules/workbox.html#adding-custom-service-worker">このあたり</a>を参照。</p>
<pre><code class="typescript">// nuxt.config.ts
import { Configuration } from "@nuxt/types";
const config: Configuration = {
// ...略
modules: [
// ... 略
"@nuxtjs/pwa",
],
workbox: {
// 追加するスクリプトを指定。
// バンドルされないので、CDNのfirebase-appを追加しておく。
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"
],
// 開発中でもsw.jsが生成されるように設定。
dev: process.env.MODE != "production",
},
// ...略
};
export default config;
</code></pre>
<h4 id="nuxtServerInitなどで認証状態をチェックする"><a href="#nuxtServerInit%E3%81%AA%E3%81%A9%E3%81%A7%E8%AA%8D%E8%A8%BC%E7%8A%B6%E6%85%8B%E3%82%92%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B">nuxtServerInitなどで認証状態をチェックする</a></h4>
<p>IDトークンもJWTデコードすると、UIDを取得できるけれど、<br />
有効かどうかをfirebase-adminでチェックする必要がある。</p>
<p>なので、まずは、firebase-adminのインスタンスを初期化するファイルを用意。</p>
<pre><code class="typescript">// ~/utils/firebaseAdmin.ts
let admin;
if (process.server) {
admin = require("firebase-admin");
if (!admin.apps.length) {
const serviceAccount = require("./path/to/your/key.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://your-database-url.firebaseio.com"
});
}
}
export default admin;
</code></pre>
<p>次にこれを使って、以下をしていく。</p>
<ol>
<li>リクエストヘッダからIDトークンを取得し、</li>
<li>firebase-adminを使ってIDトークンの有効性を確認</li>
</ol>
<p>以下は、<a target="_blank" rel="nofollow noopener" href="https://github.com/championswimmer/vuex-module-decorators">vuex-module-decorators</a>を使ったnuxtServerInitでチェックするサンプル。</p>
<pre><code class="typescript">// ~/store/index.ts
import { Context } from "@nuxt/types";
import { ActionContext } from "vuex/types";
import { ActionTree, Store } from "vuex";
import { initialiseStores } from "~/utils/store-accessor";
export const state = () => ({});
export type RootState = ReturnType<typeof state>;
const initializer = (store: Store<any>) => initialiseStores(store);
export const plugins = [initializer];
export const actions: ActionTree<any, any> = {
async nuxtServerInit(
context: ActionContext<RootState, RootState>,
server: Context
) {
// requestのAuthorizationからIDトークンを取得
const authorizationHeader = req.headers.authorization || "";
const components = authorizationHeader.split(" ");
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);
// 検証結果からUIDを取得
const uid = decodedClaims.uid;
// TODO: 認証状態に応じてなにかする
}
};
export * from "~/utils/store-accessor";
</code></pre>
<p>firebase-adminを取得する部分を</p>
<pre><code class="typescript">const admin = require("firebase-admin");
</code></pre>
<p>としていたり、<code>if (process.server)</code>や<code>if (!admin.apps.length)</code>などのチェックをせずにいたら、<br />
クライアント側でもバンドルされていて、うまく動かいない状態に...</p>
<h4 id="注意点"><a href="#%E6%B3%A8%E6%84%8F%E7%82%B9">注意点</a></h4>
<p>これで認証が必要なページでもいい感じSSRできた気がする(<em>´ω`</em>)</p>
<p>ただ、課題が残っていて、スーパーリロード/ハードリロードすると、<br />
Service Workderを介して、リクエストされないので、ヘッダに認証情報が付与されない...</p>
<p>ログを見ていると、スーパーリロード時には、描画されたあとに、Service Workderの登録されているよう。</p>
<p>そういった場合でも、利用したい場合には、従来のセッションCookieを利用する方法がよいかも?</p>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/admin/manage-cookies?hl=ja">セッション Cookie を管理する | Firebase</a></li>
</ul>
<p>(もし良い方法があれば、教えてほしいです...)</p>
<hr />
<h4 id="おまけ: Service Workerのライフサイクル"><a href="#%E3%81%8A%E3%81%BE%E3%81%91%3A+Service+Worker%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB">おまけ: Service Workerのライフサイクル</a></h4>
<p>ここに書いてあった。<br />
- <a target="_blank" rel="nofollow noopener" href="https://developers.google.com/web/fundamentals/primers/service-workers?hl=ja">Service Worker の紹介 | Web Fundamentals | Google Developers</a></p>
<p><img src="https://developers.google.com/web/fundamentals/primers/service-workers/images/sw-lifecycle.png?hl=ja" width="400px"/></p>
<hr />
<p>以上!!</p>
<h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2>
<p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br />
<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p>
<p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p>
<p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p>
<p>要望・感想・アドバイスなどあれば、<br />
公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p>
<h1 id="参考にしたサイト様"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E6%A7%98">参考にしたサイト様</a></h1>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/web/service-worker-sessions?hl=ja">Service Worker によるセッション管理 | Firebase</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/daishinkawa/items/915d918aba6bf7849b21#auth%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF">Firebase (Hosting × Functions) × Nuxt.js (universal) で ユーザ認証のベストプラクティスを探る旅 その2 - Qiita</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/basho/items/acd6a17bb6e2a2f7a932#section3%E6%96%B0%E8%A6%8F%E7%99%BB%E9%8C%B2google%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E5%AE%9F%E8%A3%85">SSRモードのNuxtでのFirebase認証 - Qiita</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/y_fujieda/items/f9e765ac9d89ba241154#service-worker">Service Workerの基本とそれを使ってできること - Qiita</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/horo/items/175c8fd7513138308930">ServiceWorkerとCache APIを使ってオフラインでも動くWebアプリを作る - Qiita</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle?hl=ja">Service Worker のライフサイクル | Web Fundamentals | Google Developers</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/admin/manage-sessions">ユーザー セッションの管理 | Firebase</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/admin/manage-cookies">Manage Session Cookies | Firebase</a></li>
</ul>
きらぷか@積読ハウマッチ/SSSAPIなど