こんにちは。
Firebase Hostingで展開しているSPAに、Basic認証を掛ける方法を紹介します。
注意
ここではReactやFirebaseについては解説しません。
SPAをFirebase Hostingで公開している状態から開始して、Functionsを使ってSPAへのアクセスにBasic認証を要するように変更します。
また、Functionsを利用するには従量課金制のBlazeプランにプロジェクトを移行する必要があるので、事前に移行しておいてください。
環境
今回はReactを使っていますが、Basic認証を掛ける仕組みは変わらないのでVueやAngularでも同じように出来るはずです。
また、Firebaseプロジェクトはfirebase init
でTypeScriptテンプレートかつSPA用の設定を有効化して作成しました。
諸々の環境の違いは適宜読み替えてもらえると助かります。
(コマンドの例: yarn --cwd functions add express
-> npm --prefix functions install express
)
-
package.json
{
"devDependencies": {
"firebase-tools": "^11.13.0"
}
}
functions/package.json
{
"dependencies": {
"firebase-functions": "^3.24.1"
},
"devDependencies": {
"typescript": "^4.8.4"
},
}
概要
どのようにしてBasic認証を掛けながらSPAを配信するかを簡単に書いておきます。
- Basic認証を掛けながらファイルを配信するExpressアプリをFunctionとして公開
- Hostingへのアクセスを上記のFunctionにリライト
記載するコマンドは全てプロジェクトのルートディレクトリで実行する想定で記述します。
また、ソースコードは既に存在するソースコードに組み合わせる前提なので、全体ではなく一部を抜粋しています。
ファイルツリーについても、ある程度抜粋した状態の物を記載しています。
$ ls
# -> functions/ ... src/ ... firebase.json ...
必要なパッケージのインストール
まず、必要なパッケージをインストールします。
一部のチュートリアルではbasic-auth-connect
をBasic認証ミドルウェアとして使用しますが、古くてTypeScriptに対応していないのでここではexpress-basic-auth
を使用します。
$ yarn --cwd functions add express express-basic-auth
$ yarn --cwd functions add -D @types/express
コマンドを実行したら、functions/package.json
には以下のような項目が追加されました。
{
"dependencies": {
"express": "^4.18.1",
"express-basic-auth": "^1.2.1"
},
"devDependencies": {
"@types/express": "^4.17.14"
},
}
Basic認証付き静的ファイル配信Functionの定義
Cloud Functions for Firebase上に、Basic認証を掛けながらfunctions/lib/static
ディレクトリのファイルを配信するbasic
関数を定義します。
functions/src/index.ts
// インポート周りはTypeScriptの設定に左右されるので必要に応じて調整する。
const path = require('path');
const express = require('express');
const basicAuth = require('express-basic-auth');
/** `basic`エンドポイントをハンドリングするExpressアプリケーション */
const basic = express();
/* 【ミドルウェア】Basic認証 */
basic.use(
basicAuth({
challenge: true, // ブラウザのBasic認証プロンプトを表示する。
users: { username: 'password' }, // ユーザー名/パスワードの組を入れる。
})
);
/* 【ミドルウェア】静的配信(`static`) */
basic.use(express.static(path.join(__dirname, 'static')));
/* 【ミドルウェア】全てのリクエストに対して`index.html`(SPA)を送信する処理 */
basic.use((_, res) => {
res.sendFile(path.join(__dirname, 'static', 'index.html'));
});
// `basic`エンドポイントとして公開する。
exports.basic = functions.https.onRequest(basic);
補足
basic
関数を処理するExpressアプリは以下のように3つのミドルウェアから構成されます。
- Basic認証ミドルウェア
- 静的配信ミドルウェア
- 全てのリクエストに
index.html
を返すミドルウェア
こうすると全てのリクエストに対して(1)でBasic認証を掛けながら、index.html
及びindex.html
に記述された.css
や.js
ファイルリクエストは(2)が応答、ルーティングを指定してのSPAへのアクセスには(3)が応答するので、Basic認証を掛けながらSPAを配信できるようになります。
Functionsのビルド
適当なコマンドでFunctionsをビルドします。
今回はyarn --cwd functions run build
でビルドするとfunctions/lib
ディレクトリに結果が出力される事とします。
- functions/
- lib/ # このディレクトリが生成された。
- src/
- src/
- firebase.json
SPAのビルド
適当なコマンドでSPAをビルドします。
今回はyarn run build
でビルドするとbuild
ディレクトリに結果が出力される事とします。
# ここでは以下のコマンドでビルドを行う。
$ yarn run build
以下のようなファイルが出力されると思います。
- build/ # このディレクトリが生成された。
- static/
- css/
- main.f2bef1c1.css
- js/
- main.b8bf8203.js
- index.html
- functions/
- src/
- firebase.json
ビルド済みSPAのfunctions/lib
への移動
先ほどのbuild
ディレクトリをstatic
ディレクトリにリネームして、functions/lib
に移動します。
こうするとbasic
関数を処理するExpressアプリから、ビルド結果のファイル群を配信できるようになります。
(Create React Appのビルドでは移動後のツリーがfunctions/lib/static/static
になってややこしいですが正しいです。)
- functions/
- lib/
- static/
- static/
- css/
- main.f2bef1c1.css
- js/
- main.b8bf8203.js
- index.html
- src/
- src/
- firebase.json
Firebase Hostingへのリクエストのリライト
最後に、Firebase Hostingへのリクエストを全てbasic
関数が処理するようにリライトします。
firebase.json
"hosting": {
"rewrites": [
{
"source": "**",
"function": "basic"
}
]
}
【おまけ1】FirebaseエミュレーターでのBasic認証付きSPA配信
エミュレーターではHostingのリライトが機能しないので、直接basic
関数にアクセスする必要があります。
例として、以下のようなプロジェクトを使っているとします。
- FirebaseプロジェクトID:
basic-auth-spa
この場合、basic
関数に直接アクセスする場合は以下のURLに接続します。
http://localhost:5001/basic-auth-spa/us-central1/basic
しかし、アクセスしても画面は真っ白のまま何も表示されません。
何故ならば、index.html
内に記載された.css
や.js
のパス変換が上手く機能しないからです。
エミュレーター向けにindex.html
を変更する
ビルド結果のindex.html
には、SPAの実体である.css
や.js
は以下のように読み込むように記述されています。
<!-- Create React Appの場合 -->
<script defer="defer" src="/static/js/main.b8bf8203.js"></script>
<link href="/static/css/main.f2bef1c1.css" rel="stylesheet" />
<!-- Vite + Reactの場合 -->
<script type="module" crossorigin src="/assets/index.19d121b8.js"></script>
<link rel="stylesheet" href="/assets/index.8d16f29f.css" />
これらのsrc
属性に注目してください。
src
属性は、ルートからのパスが記述されているので、http://localhost:5001/basic-auth-spa/us-central1/basic
へのリクエストで得たindex.html
から読み込まれる場合は以下のようにパスが変換されます。
-
/static/js/main.b8bf8203.js
->http://localhost:5001/static/js/main.b8bf8203.js
-
/static/css/main.f2bef1c1.css
->http://localhost:5001/static/css/main.f2bef1c1.css
-
/assets/index.19d121b8.js
->http://localhost:5001/assets/index.19d121b8.js
-
/assets/index.8d16f29f.css
->http://localhost:5001/assets/index.8d16f29f.css
これらのファイルは当然見つかりません。
そもそもbasic
関数はおろかFunctionsへのリクエストすらしていないので探してすらいません。
これらにアクセスするためには、URLが以下のようにbasic
関数のエンドポイントを基準としたパスになっている必要があります。
-
http://localhost:5001/static/js/main.b8bf8203.js
->http://localhost:5001/basic-auth-spa/us-central1/basic/static/js/main.b8bf8203.js
-
http://localhost:5001/static/css/main.f2bef1c1.css
->http://localhost:5001/basic-auth-spa/us-central1/basic/static/css/main.f2bef1c1.css
-
http://localhost:5001/assets/index.19d121b8.js
->http://localhost:5001/basic-auth-spa/us-central1/basic/assets/index.19d121b8.js
-
http://localhost:5001/assets/index.8d16f29f.css
->http://localhost:5001/basic-auth-spa/us-central1/basic/assets/index.8d16f29f.css
index.html
を直接編集する
index.html
に記述されているパスをbasic
関数基準の物に変更すると、エミュレーター環境でも正しくSPAが表示されるようになります。
最も素朴な方法としては、index.html
を直接編集します。
<!-- Create React Appの場合 -->
<script defer="defer" src="/basic-auth-spa/us-central1/basic/static/js/main.b8bf8203.js"></script>
<link href="/basic-auth-spa/us-central1/basic/static/css/main.f2bef1c1.css" rel="stylesheet" />
<!-- Vite + Reactの場合 -->
<script type="module" crossorigin src="/basic-auth-spa/us-central1/basic/assets/index.19d121b8.js"></script>
<link rel="stylesheet" href="/basic-auth-spa/us-central1/basic/assets/index.8d16f29f.css" />
Create React Appの場合は環境変数で設定する
Create React AppではPUBLIC_URL
環境変数を設定すると、上記の変更を自動で行います。
.env
PUBLIC_URL="/basic-auth-spa/us-central1/basic/"
開発時のみ有効化する
開発環境としてビルドする場合には以下のように、特定のビルドコマンドにのみ適用されるようにします。
package.json
{
"scripts": {
"build:development": "PUBLIC_URL=/basic-auth-spa/us-central1/basic/ react-scripts build"
}
}
Viteの場合も設定が存在する
base
設定がこれにあたります。
【おまけ2】Reactアプリのビルド結果の出力先を変更する
ビルドした後にいちいちリネームと移動をするのが面倒な場合は設定でそもそもの出力先を変更しましょう。
汎用的な方法
シンプルに、ビルド後にmv
コマンドでディレクトリを丸ごとリネーム&移動します。
$ yarn run build && mv build functions/lib/static
Create React Appの場合
BUILD_PATH
環境変数で出力ディレクトリを変更できます。
.env
BUILD_PATH="functions/lib/static"
Viteの場合
outDir
設定が対応します。
まとめ
以上の設定を行うと、HostingのURLにアクセスしたときにBasic認証を求められるようになります。
Firebase Hostingには標準で閲覧制限を掛ける手段が無いので、限られた人にのみ一時的に公開する場合に便利です。