Firebase Storageのデフォルトバケットでつまづいた話

プログラミング

結論:混乱の正体は「名前違い」

ポイント:今回のつまずきは、Firebase Storage の「バケットの実名」と「アクセス用ドメイン」を取り違えたことが原因でした。通常はデフォルトバケットが <プロジェクトID>.appspot.com ですが、私の環境ではなぜか <プロジェクトID>.firebasestorage.app が表示されており、ここで思い込みが生まれました。

理由:Admin SDK や Google Cloud Storage(JSON API) は“バケットの実名”を参照します。一方、Web/モバイルのクライアントSDKは firebasestorage.app ドメインを入り口として扱います。見た目が似ているため、どちらを設定すべきか迷いやすい構造になっていました。

例:システムの実装は「デフォルト=<プロジェクトID>.appspot.com」を前提にしていましたが、現実のバケットは <プロジェクトID>.firebasestorage.app でした。そのため Functions から画像を読みにいくと 404 が返り、内部エラーに連鎖しました。

  • Admin SDK:実名のバケットを使う
  • クライアントSDK:firebasestorage.app のドメイン経由
  • コンソールの「gs://…」に出る値が正解

結論:UIに表示される gs:// の値(実名)を信じ、環境変数と初期化コードで明示的に指定すると迷いが消えます。

先に起きた症状とエラー

404:バケットが存在しないと表示された

ポイント:JSON API で https://storage.googleapis.com/storage/v1/b/<プロジェクトID>.appspot.com/o/… にアクセスしたところ、The specified bucket does not exist. が返ってきました。

理由:リクエストが参照していたのは「想定上のデフォルト名」であり、実環境のバケット名と一致していなかったためです。実態は <プロジェクトID>.firebasestorage.app で、コードと現物がずれていました。

  • 表示エラー:404 not found
  • 原因:バケット名の取り違え
  • 確認:Firebase Console の Storage で gs://~ をチェック

まとめ:404 は「権限」ではなく「名前違い」のサインです。最初にバケットの実名を見直すと早道となります。

401:匿名ユーザーに閲覧権限がないと言われた

ポイント:別経路(ブラウザ)でオブジェクト URL をそのまま叩くと、Anonymous caller does not have storage.objects.get access… と表示されました。

理由:認証なしのアクセスで、セキュリティルールに合致しなかったためです。たとえバケット名が合っていても、ルールや署名付きURLの準備がなければ取得できません。

  • 表示エラー:401 Unauthorized
  • 対処:Firebase Auth を通すか、署名付きURL/公開設定を利用
  • 運用:基本は getDownloadURL() を使う

まとめ:401 は「認証・公開」の不足が原因です。名前を直した後に発生することもあるため、順番に切り分けましょう。

500:Functions 側で内部エラーが起きた

ポイント:フロントから Callable Functions を呼ぶと 500 系の内部エラーが出る場面がありました。

理由:内部で Admin SDK が誤ったバケット名を参照し、存在しないバケットに対してオブジェクト取得を試みたためです。一次原因は「名前違い」、二次的に Functions が落ちて見える形でした。

  • 症状:Functions ログにストレージ 404 → 例外
  • 原因:Admin SDK の storageBucket が未設定/誤設定
  • 対処:環境変数と初期化コードで明示

まとめ:Functions の 500 は、上流のストレージ設定ミスが飛び火した結果でした。ログでバケット名を出すと早く収束します。

2つの名の違いを理解

<ID>.appspot.com は GCS の実名(Admin 用)

ポイント:<プロジェクトID>.appspot.com は Google Cloud Storage における「バケットの正式名称」です。Admin SDK や JSON API はこの値を使って操作します。

理由:サーバー側の処理は IAM 権限で GCS を直接呼び出すため、URLドメインではなくバケットの実名を必要とします。ここを取り違えると 404 に直結します。

  • 使いどころ:Cloud Functions/バックエンド処理
  • 確認方法:Storage ルートの gs://… 表示が唯一の正解
  • 設定先:Admin SDK 初期化の storageBucket

まとめ:サーバーは“実名”で動きます。まずは Console の gs:// を写し取ることが大切です。

<ID>.firebasestorage.app は SDK の入口(クライアント用)

ポイント:<プロジェクトID>.firebasestorage.app はクライアントSDKのアクセス用ドメインです。Auth やセキュリティルールを適用しながら、ファイル配信の入り口として機能します。

理由:Web/モバイルでは認証・ルールを簡単に扱う必要があるため、SDK がこのドメインを抽象化してくれます。実名バケットを手組みで叩く必要はありません。

  • 使いどころ:ブラウザ/アプリのダウンロード・アップロード
  • 取得方法:getDownloadURL(ref) を利用
  • 混同注意:これは「バケット名」ではなく「アクセスの窓口」

まとめ:フロントはドメイン、サーバーは実名。役割を分けて考えると理解が進みます。

JSON API とダウンロード URL の違い

ポイント:GCS の JSON API は storage.googleapis.com でバケットの実名を扱います。対して、Firebase のダウンロード URL は SDK がよしなに生成し、認証やルールを通した取得を助けます。

  • JSON API:https://storage.googleapis.com/storage/v1/b/<バケット名>/o/…
  • SDK URL:https://firebasestorage.googleapis.com/v0/b/<バケット名>/o/…
  • 原則:フロントは SDK、サーバーは Admin SDK

まとめ:URLの形が違うだけでなく、想定権限やルールも変わります。用途に合った入り口を選ぶと失敗しにくくなります。

何を確認すべきか

まず「実在するバケット名」を特定する

ポイント:Firebase Console の Storage ルートに表示される gs://… が現物のバケット名です。ここが <プロジェクトID>.appspot.com とは限りません。

  • 開発・本番で値が異なるケースに注意
  • コピー&ペーストで転記して誤記を防止
  • ドキュメントにも明記しておくと安全

まとめ:「UIに出ている値こそ正解」です。推測せず、まず見る。これだけで多くの不具合が未然に防げます。

Functions(Admin SDK)の参照先をログで確認

ポイント:Admin SDK 初期化時に storageBucket と「決定元」をログ出力すると、ミスにすぐ気づけます。

  • 起動ログ例:storageBucket=…, source=env / FIREBASE_CONFIG / fallback
  • CI/CD でも出力して差分検知
  • 問題発生時の一次切り分けが圧倒的に速くなる

まとめ:可視化が最速のデバッグです。値と決定ロジックを出せば、誰でも追える状態になります。

クライアント設定の一致を確認

ポイント:Web 側の firebaseConfig.storageBucket にも、実名のバケットを設定します。SDK が内部で利用するため、ここの不一致は画像取得の失敗につながります。

  • 原則:フロントも同じ実名を設定
  • 取得は getDownloadURL() を必ず使用
  • 直叩きURLの手組みは避ける

まとめ:バックとフロントで同じ名を使うと、環境差異が減ります。設定の単純化が再発防止に効いてきます。

解決手順(最短ルート)

ポイント:環境変数と初期化コードで、使うバケットを明示的に固定しました。通常は <プロジェクトID>.appspot.com がデフォルトですが、今回は <プロジェクトID>.firebasestorage.app が実体だったため、システムの前提と噛み合っていませんでした。

実施内容:Functions 直下に functions/.env を作成して、次を設定しました。

STORAGE_BUCKET=<プロジェクトID>.firebasestorage.app

さらに、Admin SDK 初期化コードでもバケットを明示し、どの経路でも最終的にこの値が使われるように強制しています。

// functions/src/firebase.ts(例)
import { initializeApp } from 'firebase-admin/app';

const env = process.env;
const cfg = env.FIREBASE_CONFIG ? JSON.parse(env.FIREBASE_CONFIG) : {};

const STORAGE_BUCKET =
  env.STORAGE_BUCKET
  ?? cfg.storageBucket
  ?? '<プロジェクトID>.firebasestorage.app'; // Consoleの gs:// を写す

initializeApp({ storageBucket: STORAGE_BUCKET });

// 起動ログ(決定元も出すと安心)
console.log('[bootstrap] admin initialized', {
  storageBucket: STORAGE_BUCKET,
  source: env.STORAGE_BUCKET ? 'env.STORAGE_BUCKET'
    : cfg.storageBucket ? 'FIREBASE_CONFIG.storageBucket'
    : 'fallback'
});
  • 環境変数により値を固定化
  • 初期化コードで最終決定
  • ログ出力で検証しやすくする

結果:Functions の画像取得が安定し、404/500 の再発が止まりました。前提のズレを設定で吸収できた形です。

エラー別チェック表

404 のとき(まず名前を疑う)

  • Console の gs://… とコードの指定が一致しているか
  • 環境差(dev/prod)で名が違っていないか
  • スペル・大小文字のミスがないか

まとめ:404 は名前違いの警告灯です。権限より先に、名寄せを行うと早く片づきます。

401 のとき(認証・公開設定を点検)

  • ユーザーはログイン済みか
  • ダウンロードは getDownloadURL() を使用しているか
  • 公開配布なら署名付きURLや公開設定を検討

まとめ:401 は未認証アクセスのサインです。Auth とルールを見直すと改善します。

403 のとき(Functions の権限不足)

  • Functions のサービスアカウントに Storage Object Viewer 以上が付与されているか
  • 組織ポリシーでブロックされていないか
  • バケットのリージョンと関数のリージョンが整合しているか

まとめ:403 はIAM の話です。だれが何をするか、の許可を丁寧に確認します。

再発防止のベストプラクティス

環境変数と起動ログを標準化する

  • STORAGE_BUCKET を環境変数で必ず定義
  • 初期化時に値と決定元をログ出力
  • ドキュメント(README/運用メモ)に手順を残す

まとめ:「見える化」と「単一の定義場所」が再発を防ぎます。チーム全員が同じ手順で検証できる状態を維持しましょう。

命名・リージョン・環境差のルールを決める

  • 開発・本番で命名規則を統一(例:<id>-dev / <id>-prod
  • リージョンを揃えて遅延と権限の事故を回避
  • 環境ごとに .env を用意して切り替えを明確化

まとめ:最初にルールを決めると、後からのブレが起きません。設定の一貫性が品質を支えます。

まとめ

ポイント:今回つまづいた本質は「名前違い」でした。通常は <プロジェクトID>.appspot.com がデフォルトと考えがちですが、実際の環境では <プロジェクトID>.firebasestorage.app が現物だったため、システムの前提と食い違っていました。

FirebaseコンソールのStorage画面で、デフォルトのバケット名を確認すること
FirebaseコンソールのStorage画面で、デフォルトのバケット名を確認すること
  • まず Console の gs://… を確認する
  • functions/.envSTORAGE_BUCKET=<プロジェクトID>.firebasestorage.app を設定
  • Admin SDK 初期化で storageBucket を明示し、ログで検証
  • クライアントは getDownloadURL() を使い、直叩きを避ける

結び:設定を明示して可視化すると、迷いが消えます。これだけで「デフォルトバケットの落とし穴」を安全に迂回できるはずです。

コメント

タイトルとURLをコピーしました