2
3

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 3 years have passed since last update.

特定のapikeyを持つ通信のみFirebase Hostingへのアクセスを許可するようにする

Last updated at Posted at 2020-05-16

概要

Cloud Functions for Firebase を用いることで、ヘッダの値をもとにFirebase hostingへのアクセスを制限してみます。

前提

動作環境は、Microsoft Windows 10 Homeです。
Node.jsはインストールしてある前提です。(v12.14.1)

準備

Firebase CLIインストール

npm install -g firebase-tools でFirebase CLIをインストールします。

Firebase hostingプロジェクトを作成

firebase login でローカルマシンをFirebaseに接続させます。

firebase projects:list でCLI が正しくインストールされていて、アカウントにアクセスしていることをテストします。

C:\Users\nanna>firebase projects:list
√ Preparing the list of your Firebase projects
┌──────────────────────┬───────────────────┬────────────────┬──────────────────────┐
│ Project Display Name │ Project ID        │ Project Number │ Resource Location ID │
├──────────────────────┼───────────────────┼────────────────┼──────────────────────┤
│ angular-try          │ angular-try-8c667 │ 422372883147   │ us-central           │
└──────────────────────┴───────────────────┴────────────────┴──────────────────────┘

1 project(s) total.

firebase init でFirebaseプロジェクトを追加します。

C:\Users\nanna\firebase-try>firebase init

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

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

  C:\Users\nanna\firebase-try

? Are you ready to proceed? Yes
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. Hosting: Configure and deploy Firebase Hosting sites

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

? Please select an option: Create a new project
i  If you want to create a project in a Google Cloud organization or folder, please use "firebase projects:create" instead, and return to this command when you've created the project.
? Please specify a unique project id (warning: cannot be modified afterward) [6-30 characters]:
 apikey-control
? What would you like to call your project? (defaults to your project ID)
√ Creating Google Cloud Platform project
√ Adding Firebase resources to Google Cloud Platform project

=== Your Firebase project is ready! ===

Project information:
   - Project ID: apikey-control
   - Project Name: apikey-control

Firebase console is available at
https://console.firebase.google.com/project/apikey-control/overview
i  Using project apikey-control (apikey-control)

=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? public
? Configure as a single-page app (rewrite all urls to /index.html)? No
+  Wrote public/404.html
+  Wrote public/index.html

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...
i  Writing gitignore file to .gitignore...

+  Firebase initialization complete!

コマンド実施すると、Firebaseコンソールにプロジェクトが追加されます。
また、以下のようにローカルにファイルが生成されます。

C:\USERS\NANNA\FIREBASE-TRY
│  .firebaserc
│  .gitignore
│  firebase.json
│
└─public
        404.html
        index.html

Cloud Functions を初期化

firebase init functions でCloud Functions を初期化します。

C:\Users\nanna\firebase-try>firebase init functions

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

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

  C:\Users\nanna\firebase-try

Before we get started, keep in mind:

  * You are initializing in an existing Firebase project directory

? Are you ready to proceed? Yes

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

i  .firebaserc already has a default project, using apikey-control.

=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
+  Wrote functions/package.json
+  Wrote functions/index.js
+  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes

> protobufjs@6.9.0 postinstall C:\Users\nanna\firebase-try\functions\node_modules\protobufjs
> node scripts/postinstall

npm notice created a lockfile as package-lock.json. You should commit this file.
added 258 packages from 206 contributors and audited 258 packages in 10.445s

30 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities


i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

+  Firebase initialization complete!

成功すると、新たにfunctionsディレクトリが生成されます。

C:.
│  .firebaserc
│  .gitignore
│  firebase.json
│
├─functions
│      .gitignore
│      index.js
│      package-lock.json
│      package.json
│
└─public
        404.html
        index.html

取得するコンテンツを配置

今回、コンテンツ取得を目的とした通信が正当なものではない場合、ステータスコード400で400.htmlを返すようにします。
また、コンテンツとして、file-A.htmlfile-B.htmlを配置しておきます。

これらをCloud Functionsから見れるように、以下のように配置します。(クラウド上にデプロイするときには、Firebase Hostingの資材とCloud Functionsの資材は別々のところに行く。functions配下(Cloud Functions)とpublic配下(Firebase Hosting)は別々の場所にデプロイされるイメージ)

C:.
│  .firebaserc
│  .gitignore
│  firebase.json
│      
├─functions
│  │  .gitignore
│  │  index.js
│  │  package-lock.json
│  │  package.json
│  │  
│  └─public
│          400.html
│          404.html
│          file-A.html
│          file-B.html
│          
└─public
        404.html
        index.html

使わない資材を削除

今回は、Firebase Hostingは実質ドメイン取得だけのために使う構成にしました。
レスポンスは、Cloud Functions側でfunctions/public配下の資材をもとに作成するので、Firebase Hosting側のpublic配下の資材は使わないので削除します。
その結果、下記のような構成になります。

C:.
│  .firebaserc
│  .gitignore
│  firebase.json
│      
├─functions
│  │  .gitignore
│  │  index.js
│  │  package-lock.json
│  │  package.json
│  │  
│  └─public
│          400.html
│          404.html
│          file-A.html
│          file-B.html
│          
└─public

動作するように編集

functions/index.js

functions/index.jsを以下のように編集し、コンテンツ取得リクエストのパスとapikeyヘッダが想定と符合するものであればステータスコード200でコンテンツファイルを返すようにします。
また、この機能をfilterと名付けています。

const functions = require('firebase-functions');
const fs = require('fs');
const path = require('path');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
exports.filter = functions.https.onRequest((request, response) => {
    const requestPath = request.path;
    const matchFile = data.find(d => d.path === requestPath);
    let statusCode;
    let fileName;

    if (typeof matchFile === "undefined") {
        statusCode = 404;
        fileName = "404";
    } else {
        if (matchFile.apikey.some(k => k === request.headers.apikey)) {
            statusCode = 200;
            fileName = requestPath;
        } else {
            statusCode = 400;
            fileName = "400";
        }
    }

    let fileContent;
    fileContent = fs.readFileSync(path.resolve('public') + '/' + fileName + '.html');
    const html = fileContent.toString();

    response.status(statusCode).send(html);
});

const data = [
    {
        path: "/file-A",
        apikey: [
            "apikey1",
            "apikey2",
            "apikey3"
        ]
    },
    {
        path: "/file-B",
        apikey: [
            "apikey3"
        ]
    }
]

firebase.json

firebase.jsonを以下のように編集し、該当のドメイン配下に来た通信には、filterを通すようにします。

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

結果

下記のページにデプロイしています。
https://apikey-control.web.app/

https://apikey-control.web.app/file-A への通信のヘッダにapikey: apikey1orapikey: apikey2orapikey: apikey3を付ければ、晴れてfile-Aが見れます。
https://apikey-control.web.app/file-B へは、apikey: apikey3のみ許可しています。

Talend API TesterBurp Suiteなどを使って、apikeyを付与しながら確かめることができると思います。
Talend API Testerが一番手軽かもしれません。

その他

firebase.json のrewritesについて

firebase.jsonの下記の部分で、すべてのリクエストをfilterにかけることができると思っていたのですが、ルート直下?(今回だと、https://apikey-control.web.app/)のアクセスについて、public/index.htmlがある場合は、まずindex.htmlにリダイレクトに行くような動作になるようでした。
https://stackoverflow.com/questions/44871075/redirect-firebase-hosting-root-to-a-cloud-function-is-not-working

    "rewrites": [
      {
        "source": "**",
        "function": "filter"
      }
    ]

firebase serve の挙動が不可解な時がある

firebase serveを実施することで、ローカルにて挙動を確認できるのですが、私のローカルでhttps://apikey-control.web.app/にアクセスすると、https://apikey-control.web.app/404にリダイレクトされるという挙動になりました。
原因は不明なのですが、deploy後のクラウド環境では再現しなかったです。

参考

https://github.com/nannany/firebase-cloud-functions
https://firebase.google.com/docs/hosting/functions
https://qiita.com/otakky/items/1363e7b4c706dc9cd096

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?