Help us understand the problem. What is going on with this article?

Firebase HostingでSPA(React)表示 + Basic認証を同時に実現する

More than 1 year has passed since last update.

Firebase HostingにはBasic認証かけたりIP制限等の機能が標準では提供されていません。
それを実現するためにはFunctionsの機能と連携して実現します。

方針

  • まず、リクエストをpublicではなくfunctionsの(app)関数にリクエストを仕向ける。
  • rewrite先の関数で何を表示するかを決める。
  • 表示するまでに認証等の処理をかますことにより必要な処理を行う。

作業場所の準備

作業場所を作ってfirebase initをします。

cd
mkdir rewrite
cd rewrite
firebase init

FunctionsとHostingを選択。
下記のようなフォルダが作成されるはずです。

.
├── firebase.json
├── functions
└── public

まず、public内のindex.htmlを削除しておきます。ファイルが存在するとそちらが優先して表示されてしまうようです。

次にfunctionsでcreate-react-appを実行します。
いろいろ編集したらbuildして、buildフォルダを作成しておきます。

cd functions
create-react-app react-app
yarn build

公開サイト/ファイルはpublicではなく、functions/react-app/build/(index.html)に変更していきます。

実装

それぞれのファイルを編集していきます。

firebase.json(rewrite設定)

まず、firebase.jsonのrewritesを設定して全て(**)のリクエストをfunction, appに転送します。

firebase.json
{
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "function": "app"
      }
    ]
  }
}

SPAをホスティング

認証を設定する前に、そもそもSPAアプリをfunction経由で表示する設定をします。
大きくは2つの設定を。

index.js
const functions = require('firebase-functions');
const express = require('express');
const path = require('path');


const app = express();

//とりあえずスタティックの場所指定
app.use(express.static(__dirname + '/react-app/build/'));

//それ以外の時もindex.htmlへ
app.all('*', (req, res, next) => {
    res.sendFile(path.resolve(__dirname, 'react-app/build/index.html'));
})

exports.app = functions.https.onRequest(app);

deploy

一旦ここでdeployしておきます。

firebase deploy

reactの標準画面が見えればOKです。
一見、Hostingを利用しているのと変わりませんが、functions経由で表示されています。

Basic認証

Basic認証を設定します。まずモジュールをインストールします。

npm isntall --save basic-auth-connect

ここではID=admin, password=passwordに設定しています。

index.js
const functions = require('firebase-functions');
const express = require('express');
const path = require('path');
+const basicAuth = require('basic-auth-connect');

const app = express();

+app.use(basicAuth('admin','password'));

//とりあえずスタティックの場所指定
app.use(express.static(__dirname + '/react-app/build/'));

//それ以外の時もindex.htmlへ
app.all('*', (req, res, next) => {
    res.sendFile(path.resolve(__dirname, 'react-app/build/index.html'));
})

exports.app = functions.https.onRequest(app);
firebase deploy

IP制限

ついでにIP制限をも追加設定してみます。

index.js
const functions = require('firebase-functions');
const express = require('express');
const path = require('path');
const basicAuth = require('basic-auth-connect');
+const requestIp = require('request-ip');

//許可IP 43.233.87.238
+const allowedIps = ['43.233.87.238'];

const app = express();

//basic認証
app.use(basicAuth('admin', 'password'));

//IPチェック
+app.all('*', (req, res, next) => {
+    //IPチェック
+    const clientIp = requestIp.getClientIp(req);
+    const isAllowed = allowedIps.indexOf(clientIp) !== -1;
+    if (!isAllowed) {
+        res.send("You can't access this site.")
+    } else {
+        next();
+    }
+})

//とりあえずスタティックの場所指定
app.use(express.static(__dirname + '/react-app/build/'));

//それ以外の時もindex.htmlへ
app.all('*', (req, res, next) => {
    res.sendFile(path.resolve(__dirname, 'react-app/build/index.html'));
})

exports.app = functions.https.onRequest(app);

参考

zaburo
こんにちは。自分用のメモをだらだら公開しています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away