Firebase HostingとFunctionsを組み合わせてNuxt.jsサイトをBasic認証をかけて配信する

# 概要

- Firebase HostingとFirebase Functionsを利用して、Basic認証をかけた状態でNuxt.jsで作成したサイトを提供できるようにします。
- `firebase deploy`コマンド実行で、`nuxt generate`した結果をFirebase環境にdeployするようにします。
- 実例をあげるためにNuxt.jsを使っているだけで、Angular, React/Next.jsなどでも同様です。

なお、以下のrepositoryで実際にdeploy可能なものを確認することが出来ます。
https://github.com/0tsuki/example-nuxt-firebase-hosting

手っ取り早く確認したい方は以下の２点で内容をつかめると思います。
- https://github.com/0tsuki/example-nuxt-firebase-hosting/blob/v1.0/firebase.json
- https://github.com/0tsuki/example-nuxt-firebase-hosting/blob/v1.0/functions/src/onRequestBasicAuth.ts

## 前提

Nuxt.jsの開発が出来る環境があり、`firebase`コマンドが利用できる状態を前提とします。不明な場合は公式ドキュメントをご確認ください。

- https://ja.nuxtjs.org/guide/installation
- https://firebase.google.com/docs/hosting/quickstart?hl=ja

# 手順

## Firebase導入

nuxt.config.jsがあるディレクトリ直下で、firebaseのセットアップを行います。まずは、`firebase init`を実行します。

```bash
❯ firebase init

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /path/to/myapp

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Database: Deploy Firebase Realtime Database Rules
 ◯ Firestore: Deploy rules and create indexes for Firestore
 ◯ Functions: Configure and deploy Cloud Functions
 ◯ Hosting: Configure and deploy Firebase Hosting sites
 ◯ Storage: Deploy Cloud Storage security rules
```

選択肢の中からHostingとFunctionsを選択(space keyを押下)し、Return keyで決定します。
次にdefaultのFirebase projectを設定します。

```
? Please select an option: (Use arrow keys)
❯ Use an existing project
  Create a new project
  Add Firebase to an existing Google Cloud Platform project
  Don't set up a default project
```
大抵の場合、以下のどちらかを選択することになると思います。

- すでに作成済みのプロジェクトを使う場合 `Use an existing project`
- 新しく作成する場合 `Create a new project`

以上が終わると、以下の設定ファイルとFunctionsとHostingで設定したディレクトリが作成されます。


```json:.firebaserc
{
  "projects": {
    "default": "PROJECT_ID"
  }
}
```

```json:firebase.json
{
  "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint",
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}
```

## Basic認証用のFunctionsの追加

残念ながらBasic認証をカンタンに設定できるものはFirebaseには用意されていません。その代わり、Firebase Functionsを利用することでBasic認証を利用することができます。

functions以下に下記の変更を加えることで、`/../dist`ディレクトリをBasic認証経由で配信するonRequestBasicAuth functionを追加します。なお、この例ではtypescriptを利用しています。

```typescript:functions/src/index.ts
export * from './onRequestBasicAuth';
```
```ts:functions/src/onRequestBasicAuth.ts
import * as functions from 'firebase-functions';
import * as express from 'express';
const auth = require('basic-auth-connect');

const app = express();
app.use(auth(functions.config().basic_auth.id, functions.config().basic_auth.password));
app.use(express.static(__dirname + '/../dist'));

export const onRequestBasicAuth = functions.https.onRequest(app);
```
必要なpackageをinstallしてください。

```
$ cd functions
$ yarn add express basic-auth-connect
$ yarn add -D @types/express
```

`app.use(auth(/* 省略 */));`部分がBasic認証のid/passwordを設定している箇所になります。

`functions.config().basic_auth.id`と`functions.config().basic_auth.password`はFirebase Functionsで利用できる環境変数のようなものです。(参考: https://firebase.google.com/docs/functions/config-env)

今回の例でいうと、以下のように設定します。

```
$ firebase functions:config:set basic_auth.id=admin
$ firebase functions:config:set basic_auth.password=2019
```

設定した値は以下のように確認できます。

```
$ firebase functions:config:get
{
  "basic_auth": {
    "password": "2019",
    "id": "admin"
  }
}
```

なお、keyに大文字は利用できずエラーとなります。

```
$ firebase functions:config:set basicAuth.id=admin

Error: Invalid config name basicAuth.id, cannot use upper case.
```

また、注意すべき点として、今回利用するFirebase Functionsを使った配信は us-central1 regionのみで有効です。
そのため`functions.https.region('asia-northeast1').onRequest(app)`のように、regionを設定してしまうと動作しません。

## firebase.json

次に修正するのはfirebase.jsonです。こちらが完成したものになります。

```json:firebase.json
{
  "functions": {
    "predeploy": [
      "npm run generate",
      "mv dist \"$RESOURCE_DIR\"",
      "npm --prefix \"$RESOURCE_DIR\" run lint",
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ],
    "postdeploy": "rm -rf \"$RESOURCE_DIR\"/dist"
  },
  "hosting": {
    "public": "empty",
    "predeploy": "mkdir empty",
    "postdeploy": "rm -rf empty",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "function": "onRequestBasicAuth"
      }
    ]
  }
}
```

### firebase.json - functions

まずはfunctions部分から説明します。
predeployは名前の通り、functionsのdeploy前に実行する内容を記載する部分です。

- `npm run generate` nuxt generateによりdistディレクトリに静的ファイルを生成します。
- `mv dist \"$RESOURCE_DIR\"` 生成されたdistディレクトリをfunctions($RESOURCE_DIR)ディレクトリ下に移動する。

こうすることで、nuxtで作成したサイトをfunctionsのdeployに載せることができます。
postdeployでは、distディレクトリを削除しています。

### firebase.json - hosting

次にhostingです。hostingで設定したいことは次の２点です。

- Firebase Hosting自体では何も配信しない。（nuxtに関連したものは何もアップロードしない）
- Firebase Hostingに来たリクエスト全てをFunctionsで設定したonRequestBasicAuth functionに流す。

以下の部分では、emptyディレクトリを作成しdeployさせ、最後に削除する設定です。

```
"hosting": {
    "public": "empty",
    "predeploy": "mkdir empty",
    "postdeploy": "rm -rf empty",
```

次のrewrites部分がHostingに来たリクエストを指定したFunctionに流す設定箇所です。

```
    "rewrites": [
      {
        "source": "**",
        "function": "onRequestBasicAuth"
      }
    ]
```

以上で設定完了です。
`firebase deploy`コマンドが成功すれば、アクセスすることができます。

# 参考

冒頭で紹介したとおり、以下のrepositoryを使うことで、firebaseコマンドなどが実行可能であれば、npm|yarn installあとに即座にdeployして確認する事ができます。

https://github.com/0tsuki/example-nuxt-firebase-hosting

```
$ git clone https://github.com/0tsuki/example-nuxt-firebase-hosting
$ cd example-nuxt-firebase-hosting

# 申し訳ないですがyarn workspaceを使っていないディレクトリ構成となっているので、functions側も忘れずにinstallしてください。
$ yarn install && cd functions && yarn install && cd ..

# default projectを設定します。 firebase use YOUR_PROJECT_IDでも問題ありません。
$ firebase use --add

$ firebase deploy
```

## デモ

https://example-nuxt-firebase-hosting.firebaseapp.com/

- ユーザー名: admin
- パスワード: 2019