3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Firebase + SPA + Basic認証】FirebaseでBasic認証を掛けながらReactアプリを配信する

Last updated at Posted at 2022-10-04

こんにちは。
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)

{
  "devDependencies": {
    "firebase-tools": "^11.13.0"
  }
}
  • functions/package.json
{
  "dependencies": {
    "firebase-functions": "^3.24.1"
  },
  "devDependencies": {
    "typescript": "^4.8.4"
  },
}

概要

どのようにしてBasic認証を掛けながらSPAを配信するかを簡単に書いておきます。

  1. Basic認証を掛けながらファイルを配信するExpressアプリをFunctionとして公開
  2. 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つのミドルウェアから構成されます。

  1. Basic認証ミドルウェア
  2. 静的配信ミドルウェア
  3. 全てのリクエストに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には標準で閲覧制限を掛ける手段が無いので、限られた人にのみ一時的に公開する場合に便利です。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?