GetProjectId returns null on Compute Engine / Cloud Run with application default credentials
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 78/100
Hướng nghiên cứu
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.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
[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.
- Ngôn ngữ chính
- C#
- Star
- 407
- Fork
- 147
- Merge trung bình
- 1 ngày 3 giờ
- Pull request đã merge (30 ngày)
- 10
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của firebase/firebase-admin-dotnet
-
AbstractFirebaseAuth.cs has a typo (accound/account)Có thể làm lại được Pull request cho issue này đã bị đóng mà không được merge. Đang mởneeds-triage
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 72/100
firebase/firebase-admin-dotnet#334 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Sending FCM notification with Fids Return NotRegisteredCó thể làm lại được @yvonnep165 đã nhận 79 ngày trước và không có pull request nào đang mở. Đang mở
firebase/firebase-admin-dotnet#534 · 5 bình luận · 1 reaction · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Sending MulticastMessage does not work after replacing Tokens with FidsCó thể làm lại được @Doris-Ge đã nhận 107 ngày trước và không có pull request nào đang mở. Đang mởapi: messaging
firebase/firebase-admin-dotnet#530 · 2 bình luận · 2 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
firebase/firebase-admin-dotnet#526 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 42/100
firebase/firebase-admin-dotnet#504 · 1 bình luận · 2 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của firebase/firebase-admin-dotnet
Issue tương tự
-
needs-area-label
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
Maintainer thường phản hồi trong vòng 2 ngày
-
bug github_actions
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
endercodezz/BYDLO#336 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
SubtitleEdit/subtitleedit#16638 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
sebastienros/jint#4449 ·
Maintainer thường phản hồi trong vòng 1 ngày