Flutter WebアプリにBasic認証をつけてFirebaseにHostingする　

# 概要
開発環境や開発途中のFlutter Webアプリをデプロイするにあたり、誰もが見れる状態にしておくのは憚られます。本記事では、「Flutter WebでとりあえずBasic認証をつけてちょっとでも安心したい」という人が対象です。

Flutter Webそのものに、Basic認証機能を付けることはできない(と思われる)ため、今回はCloud Functionsを利用します。併せて、FirebaseにHostingするにあたり、Flutter Web特有の課題含めて整理します。
参考にさせていただいた記事は、[こちら](https://www.isoroot.jp/blog/1604/)と[こちら](https://qiita.com/567000/items/65f55eda8d7c6df09138)になります(感謝✨)。

# 環境

```shell
$ flutter --version
Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 18116933e7 (4 weeks ago) • 2021-10-15 10:46:35 -0700
Engine • revision d3ea636dc5
Tools • Dart 2.14.4

$ firebase --version
9.22.0

$ npm --version
8.1.3
```

# 実施事項

## ①ビルド(デプロイ用のコンテンツの作成)

```shell
$ flutter build web
```
ビルドに成功すると、「💪 Building with sound null safety 💪」と表示される

## ②Firebaseプロジェクトの作成
Firebaseの[コンソール](https://console.firebase.google.com/)から「プロジェクトを追加」を押下、その後画面に従い、プロジェクトを新規作成します。
本記事では、【flutter-web-basic-auth-sample】としましたが、適宜変更してください。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1424230/44208bf7-cc04-e1fd-0221-184d55b4bf14.png">

## ③Blazeプランへ切り替え

Cloud FunctionsがSparkプランでは使用できないため、有料プランのBlazeへ切り替えます。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1424230/75a0c1c2-15b7-f275-ed82-3885b42d77c0.png">

## ④FireStoreの初期設定を完了させる
実施しておかないとデプロイ時にエラーが発生するため、初期設定のプロビジョニングを済ませておきます。リージョンは、asia northeast-1が東京なのでオススメです。
具体的には、「Firestore Database」→「データベースの作成」を押下します。
※動作確認程度であれば「テストモード」で問題ありません。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1424230/19da3840-c0a5-52db-7efc-5ec516855fa3.png">

## ⑤Firebaseの初期化 
プロジェクトのルートディクレトリでFirebaseを初期化します。firebase cliのインストールについては別途ググっていただけると幸いです。

```shell
$ firebase init

① FunctionsとHostingをスペースキーで選択する
? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. 
 ◉ Functions: Configure a Cloud Functions directory and its files
❯◉ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys

② 先ほど作成したプロジェクトを指定する
? Please select an option: (Use arrow keys)
❯ Use an existing project 
  
? Select a default Firebase project for this directory: flutter-web-basic-auth-sample (flutter-web-basic-auth-sample)
i  Using project flutter-web-basic-auth-sample (flutter-web-basic-auth-sample)

=== Functions Setup

③ 今回はjavascriptを選択します。Typescriptを選択される場合、読み替えていただければ。
? What language would you like to use to write Cloud Functions? (Use arrow keys)
❯ JavaScript 

④ 今回はLintは指定しませんでした。適宜変更いただければ。
? Do you want to use ESLint to catch probable bugs and enforce style? No

⑤ 今回はYesとします。
? Do you want to install dependencies with npm now? Yes

=== Hosting Setup

⑥ 今回はpublicとします。適宜変更頂ければと。
? What do you want to use as your public directory? public

⑦ 今回はSPAとします。こちらはお好みで良いかと思います。
? Configure as a single-page app (rewrite all urls to /index.html)? Yes

⑧ 今回はGitHub連携しませんでした。こちらもお好みで良いかと思います。
? Set up automatic builds and deploys with GitHub? No
```
Firebaseの初期化に成功すると、「✔  Firebase initialization complete!」と表示されます。

## ⑥ビルドしたコンテンツをfunctionsフォルダにコピーする
2021年11月時点でFlutter Webはビルド先を指定できない([issue](https://github.com/flutter/flutter/issues/71130)) + ⑦のindex.jsでfunctions内のコンテンツを相対パスで指定できなかったため、今回はディレクトリごとコピーする方法を取りました。
※他に良い方法あればご教示いただけると嬉しく思います。

```shell
$ cp -r build/web functions 
```

## ⑦functions/index.jsを修正
[参考記事](https://qiita.com/567000/items/65f55eda8d7c6df09138)との差分点は、staticフォルダではなく、webフォルダを指定している点のみです。
さらに、IP制限もつける場合は、[こちら](https://www.isoroot.jp/blog/1604/)を参考にすると良いかもしれません。

```javascript
const functions = require('firebase-functions')
const express = require('express')
const basicAuth = require('basic-auth-connect')

const app = express()

app.all('/*', basicAuth(function(user, password) {
  return user === 'hoge' && password === 'hogehoge';
}));

app.use(express.static(__dirname + '/web/'))

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

## ⑧firebase.jsonを修正【"function": "app"】
デフォルトで生成されるファイルからの差分点は
"destination": "/index.html"が"function": "app"となっている点です。

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

## ⑨関連するモジュールをnpmでインストールする
functionsファイルに移動してexpressとbasic-auth-connectをインストールします。
コマンドとしては以下となります。

```shell
$ cd functions
$ npm install --save express
$ npm install --save basic-auth-connect
$ cd ..
```

## ⑩publicフォルダ配下を削除
publicフォルダ内のファイルを【全て】削除ください。
publicフォルダの中身がない場合に限り、basic認証のモジュール(firebase.jsonに記載の【functionのapp】)に飛んでくれるようになります。
※デフォルトだとindex.htmlしか生成されないのでコマンドとしては以下になるかと思います。

```shell
$ cd public
$ rm index.html
```

なお、Gitは空のディレクトリを認識できないようなので、ソースコードをGitで管理する場合、publicフォルダに.gitkeepファイル(中身は空ファイル)を置いておくのが良いかと思います。

```sh
$ touch .gitkeep
```
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1424230/a9592e80-29d3-11df-c86b-602e18e71796.png">

## ⑪デプロイする
ここまで多数の手順大変だったかと思います。あとはデプロイのみです。

```shell
$ firebase deploy
```

完了すると、「✔  Deploy complete!」と表示されます。
Hosting先のURLにアクセスし、動作確認してください。なお、今回のソースコードは[こちら](https://github.com/ko-izumi/flutter_web_basic_auth)に置いておきました。flutterアプリはデフォルトのカウンタから手を入れてません。

# 感想
正直、Flutter WebというよりCloud Functionsの記事寄りですが、Flutter x Cloud Functionsの組み合わせで少しハマってしまい、皆さんに同じ所で躓いて欲しくなかったため記事にしました。
誰かのお役に立てれば幸いです。

あと、少々力技で対処した感もあり、他に良い方法あればコメントや編集リクエスト等でご教示いただけると幸いです。