【Firebase + Vue】認証されたユーザだけが Cloud Storage の画像を見られる様にする

フロントの Vue から Cloud Storage 上に画像を上げる際に Storage 側のアクセスルールを誰でも可能にして、 こちら の SDK を使ってアップしていたのですが、これを認証されたユーザのみアクセスできる様にしたい場合若干面倒だったという話です。

Cloud Storage セキュリティルール

初めは以下の様に誰でも読み込み可能な状態で運用していました。

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
        allow read;
      allow write: if request.auth != null;
    }
  }
}

しかしそれだとセキュリティ的に問題はあるので、以下の様に認証されたアカウントでのみ読み書きができる様にしました。

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
        allow read, write: if request.auth != null;
    }
  }
}

この状態だと、予め firebase の認証をしていたユーザのみ storage の読み書きができます。

javascript + firebase SDK

ファイルのアップとアップ先の url の取得はだいたいこんな感じで SDK を使用して行っていました。

let fileName = `hoge`
  const storageRef = firebase.storage().ref(`/test/${fileName}.png`)
  const uploadTask = storageRef.put(this.file)
  uploadTask.on('state_changed', 
      snapshot => {
        const percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
        this.fileLoading = percentage
      },
      err => {
        console.log(err)
      },
      () => {
        uploadTask.snapshot.ref.getDownloadURL().then(this.callback)
      }
    )
  }

ただこれだと1つ問題があり、ここでいう this.callback の引数に渡される getDownloadURL() から取得できる url には token=****** という token が付与されているのですが、これが付与された url ではどこからでもアクセスできてしまいます、、、この token 付き url は認証しているユーザしか取得できないものの、これが流出してしまえば誰でもファイルにアクセスできてしまいます。

Auth token を使ってファイルをダウンロードする

そこで こちら の 認証時に取得できる Auth token を使ってファイルをダウンロードする Rest API を使います。

公式のコードサンプルには js の例はありませんが普通の Rest API なので以下の様に axios でリクエストします。

        const image = await axios.get(url, 
          {
            responseType: "arraybuffer" ,
            headers: {
              Authorization: `Bearer ${sessionStorage.getItem('authToken')}`,
            },
          })

上記の例ではログイン時に sessionStorage に格納しておいた Auth token を使ってリクエストしています。

CORS について

ただこれだとまだ CORS の問題があり、開発環境なら vue.config.js の方で proxy の設定をして上記の API を叩けるのですが、本番環境ではそうは行きません。。。そのため、Storage 側で CORS の設定を行い、特定のオリジンでの通信を許可する必要があります。

詳しくはこちらの方の記事を参考にしていますが、簡単に説明すると Google Cloud SDK の機能で以下の様にオリジンやメソッドを指定してその通信を許可させます。

[{
  "origin": ["https://xxxx.firebaseapp.com", "http://localhost:8080"],
  "responseHeader": ["*"],
  "method": ["GET"],
  "maxAgeSeconds": 86400
}]

これでどうにか CORS でブロックされずに通信を行う事が可能になります。

まとめ

何かと障壁はありましたが、これで認証ユーザのみがストレージにアクセスしてファイルの取得ができる様になります。今回はやや無理やり?な感じで静的ホスティングされているフロント側からストレージとのやりとりをしていますが、バック側で proxy 設定して取得した画像をフロントに返してもらったり、firebase-admin のgetSignedUrlというメソッドを使って期限付き url をバックから返してもらう(firebase-admin がクライアント側で使えないため)という方法の方がスマートな作りなのかもしれません。

参考書籍

こちらはVue + Firebase の書籍

こちらは Nuxt + Firebase

その他参考書類も