GetProjectId returns null on Compute Engine / Cloud Run with application default credentials
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 78/100
調査の方向性
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):
FirebaseAdmin/FirebaseAdmin/FirebaseApp.cs:309-332: the project ID only comes fromAppOptions, a service-account credential or two environment variables.FirebaseAdmin/FirebaseAdmin/Messaging/FirebaseMessagingClient.cs:55-62andFirebaseAdmin/FirebaseAdmin/Auth/Jwt/FirebaseTokenVerifier.cs:98-103: callers fail when it is null.
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:
- Run on a Compute Engine instance (or Cloud Run, GKE) with no
GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT. - 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
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
firebase/firebase-admin-dotnet のほかの issue
-
AbstractFirebaseAuth.cs has a typo (accound/account)再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープンneeds-triage
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
firebase/firebase-admin-dotnet#334 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Sending FCM notification with Fids Return NotRegistered再び着手できるかも @yvonnep165 が 78 日前に担当しましたが、オープン中のプルリクエストはありません。 オープン
firebase/firebase-admin-dotnet#534 · コメント 5 件 · リアクション 1 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
Sending MulticastMessage does not work after replacing Tokens with Fids再び着手できるかも @Doris-Ge が 106 日前に担当しましたが、オープン中のプルリクエストはありません。 オープンapi: messaging
firebase/firebase-admin-dotnet#530 · コメント 2 件 · 担当者 2 名 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 35/100
firebase/firebase-admin-dotnet#526 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 42/100
firebase/firebase-admin-dotnet#504 · コメント 1 件 · リアクション 2 件 ·
メンテナーはふだん 1 日以内に返信
firebase/firebase-admin-dotnet の issue をすべて見る
似ている issue
-
難易度 1/5 1〜3時間 初心者へのやさしさ 78/100
QuantConnect/Documentation#2838 ·
メンテナーはふだん 1 日以内に返信
-
"Create your app": .sln/.slnx step and Solution Explorer instructions don't match current C# Dev Kitオープンarea-tutorials needs-triage
難易度 1/5 1〜3時間 初心者へのやさしさ 72/100
dotnet/website-feedback#1844 ·
-
Needs: Attention :wave: v5 v5-preview5
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
Azure/azure-functions-core-tools#5852 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
OrchardCMS/OrchardCore#20194 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 69/100
Gothic-Unity-Project/Gothic-Unity#487 ·
メンテナーはふだん 1 日以内に返信