GetProjectId returns null on Compute Engine / Cloud Run with application default credentials

オープン
#588 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@lahirumaramba がすでに取り組んでいます。

2026年11月2日 から。

  • #590 @lahirumaramba による — オープン

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
78/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
csharp, google-cloud
領域
api, backend

調査の方向性

Start with FirebaseAdmin/FirebaseAdmin/FirebaseApp.cs, especially GetProjectId, and read the supplied ProjectIdDiscoveryTest.cs test and its command for running it. The issue suggests consulting Google.Api.Gax.Platform for ComputeCredential after existing lookups. Done when the metadata-server test discovers metadata-project without explicit project-ID settings and the relevant tests pass.

索引モデルが issue の本文から書いたものです。

説明

[READ] Step 1: Are you in the right place?

This is about the code in this repository (FirebaseApp.GetProjectId).

[REQUIRED] Step 2: Describe your environment
  • Operating System version: Linux x64
  • Firebase SDK version: 3.7.0 (main, c08a653)
  • Firebase Product: auth, messaging (anything that needs the project ID)
  • .NET version: .NET 6.0 test target (SDK 10.0.403)
  • OS: Linux (reproduced against a local metadata server emulator, not on a real Compute Engine instance)
[REQUIRED] Step 3: Describe the problem

Location (firebase/firebase-admin-dotnet @ c08a653):

        internal string GetProjectId()
        {
            if (!string.IsNullOrEmpty(this.Options.ProjectId))
            {
                return this.Options.ProjectId;
            }

            var projectId = this.Options.Credential.ToServiceAccountCredential()?.ProjectId;
            if (!string.IsNullOrEmpty(projectId))
            {
                return projectId;
            }

            foreach (var variableName in new[] { "GOOGLE_CLOUD_PROJECT", "GCLOUD_PROJECT" })
            {
                projectId = Environment.GetEnvironmentVariable(variableName);
                if (!string.IsNullOrEmpty(projectId))
                {
                    return projectId;
                }
            }

            return null;
        }

Problem

On Google Cloud managed environments, GoogleCredential.GetApplicationDefault() gives a ComputeCredential, and ToServiceAccountCredential() returns null for it. Unless AppOptions.ProjectId or GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT is set, GetProjectId() returns null, although the metadata server knows the project. Services then fail: FirebaseMessaging with Project ID is required to access messaging service..., ID token verification with Must initialize FirebaseApp with a project ID to verify ID tokens.

Setting the project ID explicitly works, and the messaging error message says so; this is about not having to when the environment already provides it, as firebase-admin-node does. The doc comment of GetProjectId describes the current lookup order, so this is a gap rather than a broken promise.

Steps to reproduce:
  1. Run on a Compute Engine instance (or Cloud Run, GKE) with no GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT.
  2. Initialize with application default credentials and no project ID, then use a service that needs it.

Output of the test below (the metadata server is emulated with a local HttpListener through GCE_METADATA_HOST, which Google.Apis.Auth and Google.Api.Gax both honour). Run from FirebaseAdmin/: dotnet test FirebaseAdmin.Tests/FirebaseAdmin.Tests.csproj -f net6.0 --filter "FullyQualifiedName~ProjectIdDiscoveryTest":

  Failed FirebaseAdmin.Tests.ProjectIdDiscoveryTest.GetProjectIdFromMetadataServerForComputeCredential [56 ms]
  Error Message:
   Assert.Equal() Failure: Strings differ
Expected: "metadata-project"
Actual:   null
Failed!  - Failed:     1, Passed:     0, Skipped:     0, Total:     1, Duration: 56 ms - FirebaseAdmin.Tests.dll (net6.0)
Relevant Code:
var app = FirebaseApp.Create(new AppOptions()
{
    Credential = GoogleCredential.GetApplicationDefault(), // ComputeCredential on GCE/Cloud Run
});

// internal; what the services use. Returns null here.
var projectId = app.GetProjectId();

// ArgumentException: Project ID is required to access messaging service. ...
var messaging = FirebaseMessaging.GetMessaging(app);
ProjectIdDiscoveryTest.cs
// Copyright 2026, Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Xunit;

namespace FirebaseAdmin.Tests
{
    public class ProjectIdDiscoveryTest : IDisposable
    {
        private const string MetadataJson = @"{
            ""instance"": {
                ""id"": ""1234567890"",
                ""zone"": ""projects/1234567890/zones/us-central1-a""
            },
            ""project"": {
                ""numericProjectId"": 1234567890,
                ""projectId"": ""metadata-project""
            }
        }";

        private readonly string savedGoogleCloudProject;
        private readonly string savedGcloudProject;
        private readonly string savedMetadataHost;
        private readonly HttpListener listener;
        private readonly ConcurrentQueue<string> requests = new ConcurrentQueue<string>();

        public ProjectIdDiscoveryTest()
        {
            this.savedGoogleCloudProject = Environment.GetEnvironmentVariable("GOOGLE_CLOUD_PROJECT");
            this.savedGcloudProject = Environment.GetEnvironmentVariable("GCLOUD_PROJECT");
            this.savedMetadataHost = Environment.GetEnvironmentVariable("GCE_METADATA_HOST");
            Environment.SetEnvironmentVariable("GOOGLE_CLOUD_PROJECT", null);
            Environment.SetEnvironmentVariable("GCLOUD_PROJECT", null);

            var port = GetFreePort();
            this.listener = new HttpListener();
            this.listener.Prefixes.Add($"http://localhost:{port}/");
            this.listener.Start();
            Task.Run(this.ServeMetadataAsync);
            Environment.SetEnvironmentVariable("GCE_METADATA_HOST", $"localhost:{port}");
        }

        [Fact]
        public void GetProjectIdFromMetadataServerForComputeCredential()
        {
            var app = FirebaseApp.Create(new AppOptions()
            {
                Credential = GoogleCredential.FromComputeCredential(new ComputeCredential()),
            });

            Assert.Equal("metadata-project", app.GetProjectId());
            Assert.NotEmpty(this.requests);
        }

        public void Dispose()
        {
            Environment.SetEnvironmentVariable("GOOGLE_CLOUD_PROJECT", this.savedGoogleCloudProject);
            Environment.SetEnvironmentVariable("GCLOUD_PROJECT", this.savedGcloudProject);
            Environment.SetEnvironmentVariable("GCE_METADATA_HOST", this.savedMetadataHost);
            this.listener.Close();
            FirebaseApp.DeleteAll();
        }

        private static int GetFreePort()
        {
            var socket = new TcpListener(IPAddress.Loopback, 0);
            socket.Start();
            var port = ((IPEndPoint)socket.LocalEndpoint).Port;
            socket.Stop();
            return port;
        }

        private async Task ServeMetadataAsync()
        {
            while (this.listener.IsListening)
            {
                HttpListenerContext context;
                try
                {
                    context = await this.listener.GetContextAsync().ConfigureAwait(false);
                }
                catch (Exception)
                {
                    return;
                }

                this.requests.Enqueue(context.Request.RawUrl);
                var body = Encoding.UTF8.GetBytes(
                    context.Request.RawUrl.Contains("project-id") ? "metadata-project" : MetadataJson);
                context.Response.AddHeader("Metadata-Flavor", "Google");
                context.Response.ContentType = "application/json";
                context.Response.OutputStream.Write(body, 0, body.Length);
                context.Response.Close();
            }
        }
    }
}

Already fixed in firebase/firebase-admin-node

PR: https://github.com/firebase/firebase-admin-node/pull/755 — "fix(auth): Fixed the ability to discover the project ID when running on GCP managed environments" (merged 2020-01-10T22:19:46Z, merge commit 2cfa51add9a). When no project ID is configured explicitly and the app uses ComputeEngineCredential, findProjectId asks the metadata service and caches the result.

How firebase-admin-node fixed it (relevant hunks, verbatim):

--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -82,17 +83,28 @@ export function getProjectId(app: FirebaseApp): string | null {
...
 export function findProjectId(app: FirebaseApp): Promise<string | null> {
-  return Promise.resolve(getProjectId(app));
+  const projectId = getExplicitProjectId(app);
+  if (projectId) {
+    return Promise.resolve(projectId);
+  }
+
+  const credential = app.options.credential;
+  if (credential instanceof ComputeEngineCredential) {
+    return credential.getProjectId();
+  }
+
+  return Promise.resolve(null);
 }
--- a/src/auth/credential.ts
+++ b/src/auth/credential.ts
@@ -209,10 +210,21 @@ export class ComputeEngineCredential implements Credential {
   }
 
   public getProjectId(): Promise<string> {
+    if (this.projectId) {
+      return Promise.resolve(this.projectId);
+    }
+
     const request = this.buildRequest(GOOGLE_METADATA_SERVICE_PROJECT_ID_PATH);
     return this.httpClient.send(request)
       .then((resp) => {
-        return resp.text!;
+        this.projectId = resp.text!;
+        return this.projectId;
+      })
+      .catch((err) => {
+        const detail: string = (err instanceof HttpError) ? getDetailFromResponse(err.response) : err.message;
+        throw new FirebaseAppError(
+          AppErrorCodes.INVALID_CREDENTIAL,
+          `Failed to determine project ID: ${detail}`);
       });
   }
 

Suggested fix

After the existing lookups, and only for a ComputeCredential, take the project ID from Google.Api.Gax.Platform, which is already a dependency through Google.Api.Gax.Rest, reads the metadata server and caches the result:

diff --git a/FirebaseAdmin/FirebaseAdmin/FirebaseApp.cs b/FirebaseAdmin/FirebaseAdmin/FirebaseApp.cs
--- a/FirebaseAdmin/FirebaseAdmin/FirebaseApp.cs
+++ b/FirebaseAdmin/FirebaseAdmin/FirebaseApp.cs
@@ -18,6 +18,7 @@ using System.Collections.Immutable;
 using System.Reflection;
 using System.Runtime.CompilerServices;
 using Google;
+using Google.Api.Gax;
 using Google.Apis.Auth.OAuth2;
 using Google.Apis.Logging;
 
@@ -302,8 +303,9 @@ namespace FirebaseAdmin
         /// Returns the Google Cloud Platform project ID associated with this Firebase app. If a
         /// project ID is specified in <see cref="AppOptions"/>, that value is returned. If not
         /// attempts to determine a project ID from the <see cref="GoogleCredential"/> used to
-        /// initialize the app. Looks up the GOOGLE_CLOUD_PROJECT environment variable when all
-        /// else fails.
+        /// initialize the app, then from the GOOGLE_CLOUD_PROJECT environment variable. When the
+        /// app uses a <see cref="ComputeCredential"/>, falls back to the project ID reported by
+        /// the metadata server of the Google Cloud environment.
         /// </summary>
         /// <returns>A project ID string or null.</returns>
         internal string GetProjectId()
@@ -328,6 +330,11 @@ namespace FirebaseAdmin
                 }
             }
 
+            if (this.Options.Credential.UnderlyingCredential is ComputeCredential)
+            {
+                return Platform.Instance().ProjectId;
+            }
+
             return null;
         }
 

Verified on c08a653 (.NET SDK 10.0.403, net6.0): the test above fails without this patch and passes with it; no other test results change (FirebaseAdmin.Tests on net6.0; net462 not run). Not yet tried on a real Google Cloud instance.

I can submit a PR for this.

Checked: #246 (FIREBASE_CONFIG), #292 (firebase-tools tokens), #339 (env-var credentials) and #439 (401 on topic subscribe) are other credential questions; searches for project id metadata, GetProjectId null, project ID Compute Engine and ComputeCredential project found no report of this.

主要言語
C#
スター
408
フォーク
147
平均マージ
1日 3時間
マージ済み PR(30日)
10

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

firebase/firebase-admin-dotnet のほかの issue

firebase/firebase-admin-dotnet の issue をすべて見る

似ている issue

C# の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。