From 9d2a2f8e7dccae96539d783e810f6a91fec0692d Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Tue, 28 Apr 2026 10:42:12 -0400 Subject: [PATCH 01/19] Add limited use token to functions --- app/src/internal/FirebaseInterops.cs | 41 +++++++++++++-- app/src/internal/HttpHelpers.cs | 4 +- functions/src/FirebaseFunctions.cs | 12 ++--- functions/src/HttpsCallableOptions.cs | 30 +++++++++++ functions/src/HttpsCallableReference.cs | 7 ++- .../Firebase/Sample/Functions/TestCase.cs | 11 ++-- .../Firebase/Sample/Functions/UIHandler.cs | 52 +++++++++++++++++++ .../Sample/Functions/UIHandlerAutomated.cs | 38 ++++++++++++++ .../testapp/functions/functions/index.js | 43 +++++++++++++++ .../testapp/functions/functions/package.json | 13 +++++ scripts/gha/restore_secrets.py | 4 ++ 11 files changed, 237 insertions(+), 18 deletions(-) create mode 100644 functions/src/HttpsCallableOptions.cs create mode 100644 functions/testapp/functions/functions/index.js create mode 100644 functions/testapp/functions/functions/package.json diff --git a/app/src/internal/FirebaseInterops.cs b/app/src/internal/FirebaseInterops.cs index 11a59a67b..afbc4907b 100755 --- a/app/src/internal/FirebaseInterops.cs +++ b/app/src/internal/FirebaseInterops.cs @@ -32,6 +32,7 @@ internal static class FirebaseInterops private static Type _appCheckType; private static MethodInfo _appCheckGetInstanceMethod; private static MethodInfo _appCheckGetTokenMethod; + private static MethodInfo _appCheckGetLimitedUseTokenMethod; private static PropertyInfo _appCheckTokenResultProperty; private static PropertyInfo _appCheckTokenTokenProperty; // Used to determine if the App Check reflection initialized successfully, and should work. @@ -153,6 +154,7 @@ private static void InitializeAppCheckReflection() { const string firebaseAppCheckTypeName = "Firebase.AppCheck.FirebaseAppCheck, Firebase.AppCheck"; const string getAppCheckTokenMethodName = "GetAppCheckTokenAsync"; + const string getLimitedUseAppCheckTokenMethodName = "GetLimitedUseAppCheckTokenAsync"; try { @@ -185,6 +187,17 @@ private static void InitializeAppCheckReflection() return; } + // Get the instance method GetLimitedUseAppCheckTokenAsync() + _appCheckGetLimitedUseTokenMethod = _appCheckType.GetMethod( + getLimitedUseAppCheckTokenMethodName, BindingFlags.Instance | BindingFlags.Public, null, + Type.EmptyTypes, null); + if (_appCheckGetLimitedUseTokenMethod == null) + { + LogError($"Could not find {getLimitedUseAppCheckTokenMethodName} method via reflection."); + return; + } + + // Should be Task Type appCheckTokenTaskType = _appCheckGetTokenMethod.ReturnType; @@ -215,7 +228,7 @@ private static void InitializeAppCheckReflection() } // Gets the AppCheck Token, assuming there is one. Otherwise, returns null. - internal static async Task GetAppCheckTokenAsync(FirebaseApp firebaseApp) + internal static async Task GetAppCheckTokenAsync(FirebaseApp firebaseApp, bool limitedUse = false) { // If AppCheck reflection failed for any reason, nothing to do. if (!_appCheckReflectionInitialized) @@ -233,8 +246,22 @@ internal static async Task GetAppCheckTokenAsync(FirebaseApp firebaseApp return null; } - // Invoke GetAppCheckTokenAsync(false) - returns a Task - object taskObject = _appCheckGetTokenMethod.Invoke(appCheckInstance, new object[] { false }); + object taskObject; + if (limitedUse) + { + if (_appCheckGetLimitedUseTokenMethod == null) + { + LogError("GetLimitedUseAppCheckTokenAsync instance via reflection."); + return null; + } + taskObject = _appCheckGetLimitedUseTokenMethod.Invoke(appCheckInstance, null); + } + else + { + // Invoke GetAppCheckTokenAsync(false) - returns a Task + taskObject = _appCheckGetTokenMethod.Invoke(appCheckInstance, new object[] { false }); + } + if (taskObject is not Task appCheckTokenTask) { LogError($"Invoking GetToken did not return a Task."); @@ -404,13 +431,17 @@ internal static async Task GetAuthTokenAsync(FirebaseApp firebaseApp) } // Adds the other Firebase tokens to the HttpRequest, as available. - internal static async Task AddFirebaseTokensAsync(HttpRequestMessage request, FirebaseApp firebaseApp, string authTokenPrefix = "Firebase") + internal static async Task AddFirebaseTokensAsync(HttpRequestMessage request, FirebaseApp firebaseApp, string authTokenPrefix = "Firebase", bool limitedUseAppCheckTokens = false) { - string appCheckToken = await GetAppCheckTokenAsync(firebaseApp); + string appCheckToken = await GetAppCheckTokenAsync(firebaseApp, limitedUseAppCheckTokens); if (!string.IsNullOrEmpty(appCheckToken)) { request.Headers.Add(appCheckHeader, appCheckToken); } + else if (limitedUseAppCheckTokens) + { + throw new InvalidOperationException("Failed to retrieve Limited Use App Check Token."); + } string authToken = await GetAuthTokenAsync(firebaseApp); if (!string.IsNullOrEmpty(authToken)) diff --git a/app/src/internal/HttpHelpers.cs b/app/src/internal/HttpHelpers.cs index 917069fdd..69de60a17 100755 --- a/app/src/internal/HttpHelpers.cs +++ b/app/src/internal/HttpHelpers.cs @@ -24,7 +24,7 @@ namespace Firebase.Internal // Helper functions to help handling the Http calls. internal static class HttpHelpers { - internal static async Task SetRequestHeaders(HttpRequestMessage request, FirebaseApp firebaseApp, string authPrefix = "Firebase") + internal static async Task SetRequestHeaders(HttpRequestMessage request, FirebaseApp firebaseApp, string authPrefix = "Firebase", bool limitedUseAppCheckTokens = false) { request.Headers.Add("x-goog-api-key", firebaseApp.Options.ApiKey); string version = FirebaseInterops.GetVersionInfoSdkVersion(); @@ -35,7 +35,7 @@ internal static async Task SetRequestHeaders(HttpRequestMessage request, Firebas request.Headers.Add("X-Firebase-AppVersion", UnityEngine.Application.version); } // Add additional Firebase tokens to the header. - await FirebaseInterops.AddFirebaseTokensAsync(request, firebaseApp, authPrefix); + await FirebaseInterops.AddFirebaseTokensAsync(request, firebaseApp, authPrefix, limitedUseAppCheckTokens); } // Helper function to throw an exception if the Http Response indicates failure. diff --git a/functions/src/FirebaseFunctions.cs b/functions/src/FirebaseFunctions.cs index bab1cc27a..74d7f30b0 100644 --- a/functions/src/FirebaseFunctions.cs +++ b/functions/src/FirebaseFunctions.cs @@ -228,22 +228,22 @@ private string GetUrl(in string name) { /// /// Creates a given a name. /// - public HttpsCallableReference GetHttpsCallable(string name) { - return new HttpsCallableReference(this, GetUrl(name)); + public HttpsCallableReference GetHttpsCallable(string name, HttpsCallableOptions options = null) { + return new HttpsCallableReference(this, GetUrl(name), options); } /// /// Creates a given a URL. /// - public HttpsCallableReference GetHttpsCallableFromURL(string url) { - return new HttpsCallableReference(this, url); + public HttpsCallableReference GetHttpsCallableFromURL(string url, HttpsCallableOptions options = null) { + return new HttpsCallableReference(this, url, options); } /// /// Creates a given a URL. /// - public HttpsCallableReference GetHttpsCallableFromURL(Uri url) { - return GetHttpsCallableFromURL(url.ToString()); + public HttpsCallableReference GetHttpsCallableFromURL(Uri url, HttpsCallableOptions options = null) { + return GetHttpsCallableFromURL(url.ToString(), options); } /// diff --git a/functions/src/HttpsCallableOptions.cs b/functions/src/HttpsCallableOptions.cs new file mode 100644 index 000000000..d54e2df9a --- /dev/null +++ b/functions/src/HttpsCallableOptions.cs @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Google LLC + * + * 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. + */ + +namespace Firebase.Functions +{ + /// + /// Options to configure a Callable Function reference. + /// + public sealed class HttpsCallableOptions + { + /// + /// If set to true, uses limited use App Check token for callable function requests from this + /// instance of Functions. By default, this is false. + /// + public bool LimitedUseAppCheckTokens { get; set; } + } +} diff --git a/functions/src/HttpsCallableReference.cs b/functions/src/HttpsCallableReference.cs index 47eb19ee7..f967747fb 100644 --- a/functions/src/HttpsCallableReference.cs +++ b/functions/src/HttpsCallableReference.cs @@ -33,13 +33,15 @@ public sealed class HttpsCallableReference // Functions object this reference was created from. private readonly FirebaseFunctions _firebaseFunctions; private readonly string _url; + private readonly HttpsCallableOptions _options; /// /// Construct a wrapper around the HttpsCallableReferenceInternal object. /// - internal HttpsCallableReference(FirebaseFunctions functions, string url) + internal HttpsCallableReference(FirebaseFunctions functions, string url, HttpsCallableOptions options = null) { _firebaseFunctions = functions; _url = url; + _options = options; } /// @@ -90,7 +92,8 @@ private async Task InternalCallAsync(object data) HttpRequestMessage request = new(HttpMethod.Post, _url); // Functions uses Bearer tokens for authentication. // This is different from the default Firebase token prefix used by other Firebase services. - await HttpHelpers.SetRequestHeaders(request, _firebaseFunctions.App, "Bearer"); + bool limitedUseAppCheckTokens = _options != null && _options.LimitedUseAppCheckTokens; + await HttpHelpers.SetRequestHeaders(request, _firebaseFunctions.App, "Bearer", limitedUseAppCheckTokens); request.Content = MakeFunctionsRequest(data); #if FIREBASE_LOG_REST_CALLS diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs b/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs index ddcaa26a0..7205d0db7 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs @@ -32,18 +32,23 @@ public class TestCase { // The error code expected to be returned from the function. public FunctionsErrorCode ExpectedError { get; private set; } + // The options to pass to the function. + public HttpsCallableOptions Options { get; private set; } + public TestCase(string name, object input, object expectedResult, - FunctionsErrorCode expectedError = FunctionsErrorCode.None) { + FunctionsErrorCode expectedError = FunctionsErrorCode.None, + HttpsCallableOptions options = null) { Name = name; Input = input; ExpectedData = expectedResult; ExpectedError = expectedError; + Options = options; } // Returns the CallableReference to be used by the test. Overridable to allow // different ways to generate the CallableReference. public virtual HttpsCallableReference GetReference(FirebaseFunctions functions) { - return functions.GetHttpsCallable(Name); + return functions.GetHttpsCallable(Name, Options); } // Runs the given test and returns whether it passed. @@ -102,7 +107,7 @@ public TestCaseWithURL(string name, System.Uri url, object input, object expecte // Generate the CallableReference using the URL public override HttpsCallableReference GetReference(FirebaseFunctions functions) { - return functions.GetHttpsCallableFromURL(URL); + return functions.GetHttpsCallableFromURL(URL, Options); } } } diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs index d1b0056c3..ff2ce97fa 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs @@ -34,6 +34,7 @@ public class UIHandler : MonoBehaviour { protected bool UIEnabled = false; private DependencyStatus dependencyStatus = DependencyStatus.UnavailableOther; protected FirebaseFunctions functions; + private string appCheckDebugToken = "REPLACE_WITH_APP_CHECK_TOKEN"; // When the app starts, check to make sure that we have // the required dependencies to use Firebase, and if not, @@ -51,6 +52,33 @@ protected virtual void Start() { } protected virtual void InitializeFirebase() { + try { + var debugFactoryType = Type.GetType("Firebase.AppCheck.DebugAppCheckProviderFactory, Firebase.AppCheck"); + var appCheckType = Type.GetType("Firebase.AppCheck.FirebaseAppCheck, Firebase.AppCheck"); + + if (debugFactoryType != null && appCheckType != null) { + DebugLog("Initializing Firebase App Check with Debug Provider via reflection"); + var instanceProp = debugFactoryType.GetProperty("Instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); + var debugFactoryInstance = instanceProp.GetValue(null); + + var setDebugTokenMethod = debugFactoryType.GetMethod("SetDebugToken", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); + setDebugTokenMethod.Invoke(debugFactoryInstance, new object[] { appCheckDebugToken }); + + var setProviderFactoryMethod = appCheckType.GetMethod("SetAppCheckProviderFactory", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); + setProviderFactoryMethod.Invoke(null, new object[] { debugFactoryInstance }); + + // Initialize the AppCheck instance so it's available. + var getInstanceMethod = appCheckType.GetMethod("GetInstance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public, null, new Type[] { typeof(FirebaseApp) }, null); + getInstanceMethod.Invoke(null, new object[] { FirebaseApp.DefaultInstance }); + + DebugLog("Successfully initialized App Check via reflection"); + } else { + DebugLog("AppCheck assembly not found, skipping initialization"); + } + } catch (Exception e) { + DebugLog("Failed to initialize App Check via reflection: " + e.Message); + } + functions = FirebaseFunctions.DefaultInstance; UIEnabled = true; @@ -93,6 +121,9 @@ protected void GUIDisplayTests() { if (GUILayout.Button("addNumbers")) { StartCoroutine(AddNumbers(5, 7)); } + if (GUILayout.Button("addNumbers (Limited Use)")) { + StartCoroutine(AddNumbersLimitedUse(5, 7)); + } GUILayout.EndVertical(); } @@ -117,6 +148,27 @@ protected IEnumerator AddNumbers(int firstNumber, int secondNumber) { yield return new WaitUntil(() => task.IsCompleted); } + protected IEnumerator AddNumbersLimitedUse(int firstNumber, int secondNumber) { + var func = functions.GetHttpsCallable("addNumbers", new HttpsCallableOptions { LimitedUseAppCheckTokens = true }); + var data = new Dictionary(); + data["firstNumber"] = firstNumber; + data["secondNumber"] = secondNumber; + + var task = func.CallAsync(data).ContinueWithOnMainThread((callTask) => { + if (callTask.IsFaulted) { + // The function unexpectedly failed. + DebugLog("FAILED!"); + DebugLog(String.Format(" Error: {0}", callTask.Exception)); + return; + } + + // The function succeeded. + var result = (IDictionary)callTask.Result.Data; + DebugLog(String.Format("AddNumbers (Limited Use): {0}", result["operationResult"])); + }); + yield return new WaitUntil(() => task.IsCompleted); + } + // Render the buttons and other controls. void GUIDisplayControls() { if (UIEnabled) { diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs index f4b377231..4e88c8e27 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs @@ -10,6 +10,7 @@ namespace Firebase.Sample.Functions { public class UIHandlerAutomated : UIHandler { private Firebase.Sample.AutomatedTestRunner testRunner; private Firebase.Auth.FirebaseAuth firebaseAuth; + private string appCheckDebugTokenForAutomated = "REPLACE_WITH_APP_CHECK_TOKEN"; // Returns the set of all integration tests. public static IEnumerable AllTests() { @@ -38,8 +39,18 @@ public static IEnumerable AllTests() { yield return new TestCase("dataTest", data, expected); } + { + var data = new Dictionary(); + data["firstNumber"] = 5; + data["secondNumber"] = 7; + var expected = new Dictionary(); + expected["operationResult"] = 12L; + yield return new TestCase("addNumbers", data, expected, FunctionsErrorCode.None, new HttpsCallableOptions { LimitedUseAppCheckTokens = true }); + } + var empty = new Dictionary(); yield return new TestCase("scalarTest", 17, 76L); + yield return new TestCase("scalarTest", 17, 76L, FunctionsErrorCode.None, new HttpsCallableOptions { LimitedUseAppCheckTokens = true }); yield return new TestCase("tokenTest", empty, empty); // Only run this on iOS and Android. // yield return new TestCase("instanceIdTest", empty, empty); @@ -80,6 +91,33 @@ protected override void Start() { } protected override void InitializeFirebase() { + try { + var debugFactoryType = Type.GetType("Firebase.AppCheck.DebugAppCheckProviderFactory, Firebase.AppCheck"); + var appCheckType = Type.GetType("Firebase.AppCheck.FirebaseAppCheck, Firebase.AppCheck"); + + if (debugFactoryType != null && appCheckType != null) { + DebugLog("Initializing Firebase App Check with Debug Provider via reflection"); + var instanceProp = debugFactoryType.GetProperty("Instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); + var debugFactoryInstance = instanceProp.GetValue(null); + + var setDebugTokenMethod = debugFactoryType.GetMethod("SetDebugToken", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); + setDebugTokenMethod.Invoke(debugFactoryInstance, new object[] { appCheckDebugTokenForAutomated }); + + var setProviderFactoryMethod = appCheckType.GetMethod("SetAppCheckProviderFactory", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); + setProviderFactoryMethod.Invoke(null, new object[] { debugFactoryInstance }); + + // Initialize the AppCheck instance so it's available. + var getInstanceMethod = appCheckType.GetMethod("GetInstance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public, null, new Type[] { typeof(FirebaseApp) }, null); + getInstanceMethod.Invoke(null, new object[] { FirebaseApp.DefaultInstance }); + + DebugLog("Successfully initialized App Check via reflection"); + } else { + DebugLog("AppCheck assembly not found, skipping initialization"); + } + } catch (Exception e) { + DebugLog("Failed to initialize App Check via reflection: " + e.Message); + } + // One of the automated tests requires Auth, so we want to sign in before running it. firebaseAuth = Firebase.Auth.FirebaseAuth.DefaultInstance; firebaseAuth.SignInAnonymouslyAsync().ContinueWithOnMainThread( diff --git a/functions/testapp/functions/functions/index.js b/functions/testapp/functions/functions/index.js new file mode 100644 index 000000000..fe470e646 --- /dev/null +++ b/functions/testapp/functions/functions/index.js @@ -0,0 +1,43 @@ +// 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. + +const functions = require("firebase-functions"); + +// Creates a function that consumes limited-use App Check tokens +exports.addtwowithlimiteduse = functions.runWith({ + enforceAppCheck: true, + consumeAppCheckToken: true, + maxInstances: 10 // Setting maxInstances the V1 way +}).https.onCall((data, context) => { + // context.app will be defined if a valid App Check token was provided + if (context.app === undefined) { + throw new functions.https.HttpsError( + 'failed-precondition', + 'The function must be called from an App Check verified app.'); + } + + const firstNumber = data.firstNumber; + const secondNumber = data.secondNumber; + + if (firstNumber === undefined || secondNumber === undefined) { + throw new functions.https.HttpsError('invalid-argument', 'The function must be called with "firstNumber" and "secondNumber".'); + } + + return { + firstNumber: firstNumber, + secondNumber: secondNumber, + operator: '+', + operationResult: Number(firstNumber) + Number(secondNumber), + }; +}); diff --git a/functions/testapp/functions/functions/package.json b/functions/testapp/functions/functions/package.json new file mode 100644 index 000000000..8725be2c1 --- /dev/null +++ b/functions/testapp/functions/functions/package.json @@ -0,0 +1,13 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "engines": { + "node": "20" + }, + "main": "index.js", + "dependencies": { + "firebase-admin": "^11.5.0", + "firebase-functions": "^4.2.1" + }, + "private": true +} diff --git a/scripts/gha/restore_secrets.py b/scripts/gha/restore_secrets.py index d69560b8c..1b21d98bc 100644 --- a/scripts/gha/restore_secrets.py +++ b/scripts/gha/restore_secrets.py @@ -129,6 +129,10 @@ def main(argv): _patch_file(file_path, "REPLACE_WITH_APP_CHECK_TOKEN", debug_token) file_path = os.path.join(repo_dir, "app_check", "testapp", "Assets", "Firebase", "Sample", CAPITALIZATIONS["app_check"], "UIHandler.cs") _patch_file(file_path, "REPLACE_WITH_APP_CHECK_TOKEN", debug_token) + file_path = os.path.join(repo_dir, "functions", "testapp", "Assets", "Firebase", "Sample", CAPITALIZATIONS["functions"], "UIHandlerAutomated.cs") + _patch_file(file_path, "REPLACE_WITH_APP_CHECK_TOKEN", debug_token) + file_path = os.path.join(repo_dir, "functions", "testapp", "Assets", "Firebase", "Sample", CAPITALIZATIONS["functions"], "UIHandler.cs") + _patch_file(file_path, "REPLACE_WITH_APP_CHECK_TOKEN", debug_token) print("Attempting to decrypt GCS service account key file.") decrypted_key_file = os.path.join(secrets_dir, "gcs_key_file.json") From e0db1eea4aba3f8335f68238f71b8ee13302e47f Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Tue, 28 Apr 2026 12:39:30 -0400 Subject: [PATCH 02/19] docs: add release notes for limited use functions token --- docs/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/readme.md b/docs/readme.md index 062960732..d43361454 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -121,6 +121,7 @@ Release Notes - Storage: Added `ListAsync` API to list items and prefixes under a reference. - Functions: Fixed tgz export, added missing asmdef for functions. Fixes issue where Functions were not being exported correctly in the tgz build. - Firebase AI: Fix tgz export, added missing asmdef for Firebase AI. Fixes issue where Firebase AI was not being exported correctly in the tgz build. + - Functions: Added support for passing and enforcing Limited Use App Check tokens. Throws an InvalidOperationException if a required token cannot be fetched. ### 13.10.0 - Changes From 17151669524ede85504df5805e9c17773aafe4bc Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Tue, 28 Apr 2026 12:57:10 -0400 Subject: [PATCH 03/19] Make tests clearer by adding in testing name seperate from function name --- .../Firebase/Sample/Functions/TestCase.cs | 10 ++++++--- .../Sample/Functions/UIHandlerAutomated.cs | 22 +++++++++---------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs b/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs index 7205d0db7..dbc28a403 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs @@ -20,9 +20,12 @@ namespace Firebase.Sample.Functions { using System.Threading.Tasks; public class TestCase { - // The name of the HTTPS callable function to call. + // The display name of the test. public string Name { get; private set; } + // The name of the HTTPS callable function to call. + public string FunctionName { get; private set; } + // The parameters to pass to the function. public object Input { get; private set; } @@ -35,10 +38,11 @@ public class TestCase { // The options to pass to the function. public HttpsCallableOptions Options { get; private set; } - public TestCase(string name, object input, object expectedResult, + public TestCase(string name, string functionName, object input, object expectedResult, FunctionsErrorCode expectedError = FunctionsErrorCode.None, HttpsCallableOptions options = null) { Name = name; + FunctionName = functionName; Input = input; ExpectedData = expectedResult; ExpectedError = expectedError; @@ -48,7 +52,7 @@ public TestCase(string name, object input, object expectedResult, // Returns the CallableReference to be used by the test. Overridable to allow // different ways to generate the CallableReference. public virtual HttpsCallableReference GetReference(FirebaseFunctions functions) { - return functions.GetHttpsCallable(Name, Options); + return functions.GetHttpsCallable(FunctionName, Options); } // Runs the given test and returns whether it passed. diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs index 4e88c8e27..d26a6f48c 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs @@ -36,7 +36,7 @@ public static IEnumerable AllTests() { expectedArray.Add(3L); expected["array"] = expectedArray; - yield return new TestCase("dataTest", data, expected); + yield return new TestCase("dataTest", "dataTest", data, expected); } { @@ -45,25 +45,25 @@ public static IEnumerable AllTests() { data["secondNumber"] = 7; var expected = new Dictionary(); expected["operationResult"] = 12L; - yield return new TestCase("addNumbers", data, expected, FunctionsErrorCode.None, new HttpsCallableOptions { LimitedUseAppCheckTokens = true }); + yield return new TestCase("addNumbersWithLimitedUse", "addNumbers", data, expected, FunctionsErrorCode.None, new HttpsCallableOptions { LimitedUseAppCheckTokens = true }); } var empty = new Dictionary(); - yield return new TestCase("scalarTest", 17, 76L); - yield return new TestCase("scalarTest", 17, 76L, FunctionsErrorCode.None, new HttpsCallableOptions { LimitedUseAppCheckTokens = true }); - yield return new TestCase("tokenTest", empty, empty); + yield return new TestCase("scalarTest", "scalarTest", 17, 76L); + yield return new TestCase("scalarTestwithLimitedUse", "scalarTest", 17, 76L, FunctionsErrorCode.None, new HttpsCallableOptions { LimitedUseAppCheckTokens = true }); + yield return new TestCase("tokenTest", "tokenTest", empty, empty); // Only run this on iOS and Android. - // yield return new TestCase("instanceIdTest", empty, empty); - yield return new TestCase("nullTest", null, null); + // yield return new TestCase("instanceIdTest", "instanceIdTest", empty, empty); + yield return new TestCase("nullTest", "nullTest", null, null); // Test various error cases. - yield return new TestCase("missingResultTest", null, null, + yield return new TestCase("missingResultTest", "missingResultTest", null, null, FunctionsErrorCode.Internal); - yield return new TestCase("unhandledErrorTest", null, null, + yield return new TestCase("unhandledErrorTest", "unhandledErrorTest", null, null, FunctionsErrorCode.Internal); - yield return new TestCase("unknownErrorTest", null, null, + yield return new TestCase("unknownErrorTest", "unknownErrorTest", null, null, FunctionsErrorCode.Internal); - yield return new TestCase("explicitErrorTest", null, null, + yield return new TestCase("explicitErrorTest", "explicitErrorTest", null, null, FunctionsErrorCode.OutOfRange); // Test calling via Url From 2c0c3016a7560538993ba3be22fb5f2d6a859a48 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Apr 2026 09:38:30 -0400 Subject: [PATCH 04/19] Fix up missed test case change --- functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs b/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs index dbc28a403..e6d55c087 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/TestCase.cs @@ -105,7 +105,7 @@ public class TestCaseWithURL : TestCase { public TestCaseWithURL(string name, System.Uri url, object input, object expectedResult, FunctionsErrorCode expectedError = FunctionsErrorCode.None) - : base(name, input, expectedResult, expectedError) { + : base(name, url.ToString(), input, expectedResult, expectedError) { URL = url; } From 6c7eb060f27b3e992b7d5a22e572acc64aca7a91 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Apr 2026 10:11:06 -0400 Subject: [PATCH 05/19] update comment --- app/src/internal/FirebaseInterops.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/internal/FirebaseInterops.cs b/app/src/internal/FirebaseInterops.cs index afbc4907b..875b6d7ac 100755 --- a/app/src/internal/FirebaseInterops.cs +++ b/app/src/internal/FirebaseInterops.cs @@ -251,7 +251,7 @@ internal static async Task GetAppCheckTokenAsync(FirebaseApp firebaseApp { if (_appCheckGetLimitedUseTokenMethod == null) { - LogError("GetLimitedUseAppCheckTokenAsync instance via reflection."); + LogError("Failed to find GetLimitedUseAppCheckTokenAsync method via reflection."); return null; } taskObject = _appCheckGetLimitedUseTokenMethod.Invoke(appCheckInstance, null); From a37841ec4bff7a76909a918960936e2815e51606 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Apr 2026 13:11:06 -0400 Subject: [PATCH 06/19] Update the required packages --- .../integration_testing/build_testapps.json | 4 +- .../debug_single_export_json/firebaseai.json | 6 ++ .../debug_single_export_json/functions.json | 60 +++---------------- 3 files changed, 17 insertions(+), 53 deletions(-) diff --git a/scripts/gha/integration_testing/build_testapps.json b/scripts/gha/integration_testing/build_testapps.json index 35940ae48..f67dc07fb 100644 --- a/scripts/gha/integration_testing/build_testapps.json +++ b/scripts/gha/integration_testing/build_testapps.json @@ -133,13 +133,15 @@ "platforms": ["Android", "Playmode", "iOS", "tvOS", "Desktop"], "plugins": [ "FirebaseFunctions.unitypackage", - "FirebaseAuth.unitypackage" + "FirebaseAuth.unitypackage", + "FirebaseAppCheck.unitypackage" ], "provision": "Firebase_Dev_Wildcard.mobileprovision", "upm_packages": [ "com.google.external-dependency-manager-*.tgz", "com.google.firebase.app-*.tgz", "com.google.firebase.auth-*.tgz", + "com.google.firebase.app-check-*.tgz", "com.google.firebase.functions-*.tgz" ] }, diff --git a/unity_packer/debug_single_export_json/firebaseai.json b/unity_packer/debug_single_export_json/firebaseai.json index d4c60e8a8..136f71eab 100644 --- a/unity_packer/debug_single_export_json/firebaseai.json +++ b/unity_packer/debug_single_export_json/firebaseai.json @@ -152,6 +152,12 @@ "Firebase/Plugins/x86_64/FirebaseCppApp.*" ] }, + { + "importer": "DefaultImporter", + "paths": [ + "Firebase/FirebaseApp/Internal/" + ] + }, { "labels": [ "gvh_targets-windows-windows64-osx-osxintel-osxintel64-linux-linux32-linux64" diff --git a/unity_packer/debug_single_export_json/functions.json b/unity_packer/debug_single_export_json/functions.json index c90fb8e91..23288bb92 100644 --- a/unity_packer/debug_single_export_json/functions.json +++ b/unity_packer/debug_single_export_json/functions.json @@ -152,6 +152,12 @@ "Firebase/Plugins/x86_64/FirebaseCppApp.*" ] }, + { + "importer": "DefaultImporter", + "paths": [ + "Firebase/FirebaseApp/Internal/" + ] + }, { "labels": [ "gvh_targets-windows-windows64-osx-osxintel-osxintel64-linux-linux32-linux64" @@ -216,48 +222,9 @@ "name": "FirebaseFunctions.unitypackage", "imports": [ { - "importer": "PluginImporter", - "platforms": [ - "Editor", - "Standalone", - "Android" - ], - "cpu": "AnyCPU", - "paths": [ - "Firebase/Plugins/Firebase.Functions.dll", - "Firebase/Plugins/Firebase.Functions.pdb" - ] - }, - { - "importer": "PluginImporter", - "platforms": [ - "iOS", - "tvOS" - ], - "cpu": "AnyCPU", - "paths": [ - "Firebase/Plugins/iOS/Firebase.Functions.dll", - "Firebase/Plugins/iOS/Firebase.Functions.pdb" - ] - }, - { - "importer": "PluginImporter", - "platforms": [ - "iOS" - ], - "cpu": "AnyCPU", - "paths": [ - "Plugins/iOS/Firebase/libFirebaseCppFunctions.a" - ] - }, - { - "importer": "PluginImporter", - "platforms": [ - "tvOS" - ], - "cpu": "AnyCPU", + "importer": "DefaultImporter", "paths": [ - "Plugins/tvOS/Firebase/libFirebaseCppFunctions.a" + "Firebase/FirebaseFunctions/*" ] }, { @@ -267,17 +234,6 @@ "Firebase/m2repository/com/google/firebase/firebase-functions-unity/" ] }, - { - "importer": "PluginImporter", - "platforms": [ - "Editor", - "Standalone" - ], - "cpu": "x86_64", - "paths": [ - "Firebase/Plugins/x86_64/FirebaseCppFunctions*" - ] - }, { "importer": "DefaultImporter", "sections": [ From a59814adc9a4eb0f2d07cce4a7fc761096842d9d Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Apr 2026 16:31:58 -0400 Subject: [PATCH 07/19] Add functions testing to all supported platforms --- scripts/gha/integration_testing/build_testapps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gha/integration_testing/build_testapps.json b/scripts/gha/integration_testing/build_testapps.json index f67dc07fb..d844a28d0 100644 --- a/scripts/gha/integration_testing/build_testapps.json +++ b/scripts/gha/integration_testing/build_testapps.json @@ -130,7 +130,7 @@ "captial_name": "Functions", "bundle_id": "com.google.firebase.unity.functions.testapp", "testapp_path": "functions/testapp", - "platforms": ["Android", "Playmode", "iOS", "tvOS", "Desktop"], + "platforms": ["Android", "Playmode", "iOS", "tvOS", "Windows", "macOS", "Linux"], "plugins": [ "FirebaseFunctions.unitypackage", "FirebaseAuth.unitypackage", From 6c9337f088b554f783997506887674932a793e84 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 29 Apr 2026 19:10:09 -0400 Subject: [PATCH 08/19] Fix automated test with the expected results from adding --- .../Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs index d26a6f48c..336936f95 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs @@ -44,6 +44,9 @@ public static IEnumerable AllTests() { data["firstNumber"] = 5; data["secondNumber"] = 7; var expected = new Dictionary(); + expected["firstNumber"] = 5L; + expected["secondNumber"] = 7L; + expected["operator"] = "+"; expected["operationResult"] = 12L; yield return new TestCase("addNumbersWithLimitedUse", "addNumbers", data, expected, FunctionsErrorCode.None, new HttpsCallableOptions { LimitedUseAppCheckTokens = true }); } From 3a2f2845d96e290249c545011b58cdafe2c22945 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Thu, 30 Apr 2026 16:01:16 -0400 Subject: [PATCH 09/19] Testing to see what the error is --- app/src/internal/FirebaseInterops.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/internal/FirebaseInterops.cs b/app/src/internal/FirebaseInterops.cs index 875b6d7ac..d462897cd 100755 --- a/app/src/internal/FirebaseInterops.cs +++ b/app/src/internal/FirebaseInterops.cs @@ -60,9 +60,10 @@ static FirebaseInterops() private static void LogError(string message) { -#if FIREBASEAI_DEBUG_LOGGING +//TODO AustinBenoit Must revet this change before merging +//#if FIREBASEAI_DEBUG_LOGGING UnityEngine.Debug.LogError(message); -#endif +//#endif } // Cache the methods needed for FirebaseApp reflection. From 4d3f0cd6ca85b4c10ecedcc7968ea4d9e41ee9fb Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Fri, 1 May 2026 11:49:50 -0400 Subject: [PATCH 10/19] Add more logs --- app/src/internal/FirebaseInterops.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/internal/FirebaseInterops.cs b/app/src/internal/FirebaseInterops.cs index d462897cd..79ee89bb7 100755 --- a/app/src/internal/FirebaseInterops.cs +++ b/app/src/internal/FirebaseInterops.cs @@ -159,14 +159,17 @@ private static void InitializeAppCheckReflection() try { + UnityEngine.Debug.Log("[FirebaseInterops] Initializing AppCheck Reflection..."); // Set this to false, to allow easy failing out via return. _appCheckReflectionInitialized = false; _appCheckType = Type.GetType(firebaseAppCheckTypeName); if (_appCheckType == null) { + UnityEngine.Debug.Log("[FirebaseInterops] Firebase.AppCheck assembly not found."); return; } + UnityEngine.Debug.Log("[FirebaseInterops] Found Firebase.AppCheck assembly."); // Get the static method GetInstance(FirebaseApp app) _appCheckGetInstanceMethod = _appCheckType.GetMethod( @@ -197,6 +200,7 @@ private static void InitializeAppCheckReflection() LogError($"Could not find {getLimitedUseAppCheckTokenMethodName} method via reflection."); return; } + UnityEngine.Debug.Log("[FirebaseInterops] Found GetLimitedUseAppCheckTokenAsync method."); // Should be Task @@ -220,6 +224,7 @@ private static void InitializeAppCheckReflection() return; } + UnityEngine.Debug.Log("[FirebaseInterops] AppCheck Reflection Initialized successfully."); _appCheckReflectionInitialized = true; } catch (Exception e) @@ -231,9 +236,11 @@ private static void InitializeAppCheckReflection() // Gets the AppCheck Token, assuming there is one. Otherwise, returns null. internal static async Task GetAppCheckTokenAsync(FirebaseApp firebaseApp, bool limitedUse = false) { + UnityEngine.Debug.Log($"[FirebaseInterops] GetAppCheckTokenAsync called (limitedUse={limitedUse})"); // If AppCheck reflection failed for any reason, nothing to do. if (!_appCheckReflectionInitialized) { + UnityEngine.Debug.Log("[FirebaseInterops] _appCheckReflectionInitialized is false, returning null."); return null; } @@ -288,7 +295,9 @@ internal static async Task GetAppCheckTokenAsync(FirebaseApp firebaseApp } // Get the Token property from the AppCheckToken struct - return _appCheckTokenTokenProperty.GetValue(tokenResult) as string; + string finalToken = _appCheckTokenTokenProperty.GetValue(tokenResult) as string; + UnityEngine.Debug.Log($"[FirebaseInterops] Successfully fetched App Check token (Length: {(finalToken != null ? finalToken.Length : 0)})"); + return finalToken; } catch (Exception e) { @@ -441,6 +450,7 @@ internal static async Task AddFirebaseTokensAsync(HttpRequestMessage request, Fi } else if (limitedUseAppCheckTokens) { + UnityEngine.Debug.Log($"[FirebaseInterops] Failed to retrieve limited use token. Token null? {appCheckToken == null}. Reflection Initialized? {_appCheckReflectionInitialized}"); throw new InvalidOperationException("Failed to retrieve Limited Use App Check Token."); } From 85aa4c0afa6a4a6cf350d7b5ce6ba1d704e7acf9 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Tue, 5 May 2026 11:32:51 -0400 Subject: [PATCH 11/19] Fix the overiding issues --- .../Firebase/Sample/Functions/UIHandler.cs | 34 ++++-------------- .../Sample/Functions/UIHandlerAutomated.cs | 35 +++++-------------- 2 files changed, 15 insertions(+), 54 deletions(-) diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs index ff2ce97fa..eb1c632b9 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs @@ -16,6 +16,7 @@ namespace Firebase.Sample.Functions { using Firebase; using Firebase.Extensions; using Firebase.Functions; + using Firebase.AppCheck; using System; using System.Collections; using System.Collections.Generic; @@ -43,6 +44,7 @@ protected virtual void Start() { FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => { dependencyStatus = task.Result; if (dependencyStatus == DependencyStatus.Available) { + InitializeAppCheck(); InitializeFirebase(); } else { Debug.LogError( @@ -51,34 +53,12 @@ protected virtual void Start() { }); } - protected virtual void InitializeFirebase() { - try { - var debugFactoryType = Type.GetType("Firebase.AppCheck.DebugAppCheckProviderFactory, Firebase.AppCheck"); - var appCheckType = Type.GetType("Firebase.AppCheck.FirebaseAppCheck, Firebase.AppCheck"); - - if (debugFactoryType != null && appCheckType != null) { - DebugLog("Initializing Firebase App Check with Debug Provider via reflection"); - var instanceProp = debugFactoryType.GetProperty("Instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); - var debugFactoryInstance = instanceProp.GetValue(null); - - var setDebugTokenMethod = debugFactoryType.GetMethod("SetDebugToken", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); - setDebugTokenMethod.Invoke(debugFactoryInstance, new object[] { appCheckDebugToken }); - - var setProviderFactoryMethod = appCheckType.GetMethod("SetAppCheckProviderFactory", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); - setProviderFactoryMethod.Invoke(null, new object[] { debugFactoryInstance }); - - // Initialize the AppCheck instance so it's available. - var getInstanceMethod = appCheckType.GetMethod("GetInstance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public, null, new Type[] { typeof(FirebaseApp) }, null); - getInstanceMethod.Invoke(null, new object[] { FirebaseApp.DefaultInstance }); - - DebugLog("Successfully initialized App Check via reflection"); - } else { - DebugLog("AppCheck assembly not found, skipping initialization"); - } - } catch (Exception e) { - DebugLog("Failed to initialize App Check via reflection: " + e.Message); - } + protected virtual void InitializeAppCheck() { + DebugAppCheckProviderFactory.Instance.SetDebugToken(appCheckDebugToken); + FirebaseAppCheck.SetAppCheckProviderFactory(DebugAppCheckProviderFactory.Instance); + } + protected virtual void InitializeFirebase() { functions = FirebaseFunctions.DefaultInstance; UIEnabled = true; diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs index 336936f95..8c0bd214f 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs @@ -1,4 +1,6 @@ namespace Firebase.Sample.Functions { + using Firebase; + using Firebase.AppCheck; using Firebase.Extensions; using Firebase.Functions; using System; @@ -93,34 +95,13 @@ protected override void Start() { base.Start(); } - protected override void InitializeFirebase() { - try { - var debugFactoryType = Type.GetType("Firebase.AppCheck.DebugAppCheckProviderFactory, Firebase.AppCheck"); - var appCheckType = Type.GetType("Firebase.AppCheck.FirebaseAppCheck, Firebase.AppCheck"); - - if (debugFactoryType != null && appCheckType != null) { - DebugLog("Initializing Firebase App Check with Debug Provider via reflection"); - var instanceProp = debugFactoryType.GetProperty("Instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); - var debugFactoryInstance = instanceProp.GetValue(null); - - var setDebugTokenMethod = debugFactoryType.GetMethod("SetDebugToken", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); - setDebugTokenMethod.Invoke(debugFactoryInstance, new object[] { appCheckDebugTokenForAutomated }); - - var setProviderFactoryMethod = appCheckType.GetMethod("SetAppCheckProviderFactory", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); - setProviderFactoryMethod.Invoke(null, new object[] { debugFactoryInstance }); - - // Initialize the AppCheck instance so it's available. - var getInstanceMethod = appCheckType.GetMethod("GetInstance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public, null, new Type[] { typeof(FirebaseApp) }, null); - getInstanceMethod.Invoke(null, new object[] { FirebaseApp.DefaultInstance }); - - DebugLog("Successfully initialized App Check via reflection"); - } else { - DebugLog("AppCheck assembly not found, skipping initialization"); - } - } catch (Exception e) { - DebugLog("Failed to initialize App Check via reflection: " + e.Message); - } + protected override void InitializeAppCheck() { + DebugLog("Initializing App Check directly in automated handler"); + DebugAppCheckProviderFactory.Instance.SetDebugToken(appCheckDebugTokenForAutomated); + FirebaseAppCheck.SetAppCheckProviderFactory(DebugAppCheckProviderFactory.Instance); + } + protected override void InitializeFirebase() { // One of the automated tests requires Auth, so we want to sign in before running it. firebaseAuth = Firebase.Auth.FirebaseAuth.DefaultInstance; firebaseAuth.SignInAnonymouslyAsync().ContinueWithOnMainThread( From aecdc703154913fe1880ba58068755ef962d3df0 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Tue, 5 May 2026 13:40:08 -0400 Subject: [PATCH 12/19] Clean up extra logging --- app/src/internal/FirebaseInterops.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/app/src/internal/FirebaseInterops.cs b/app/src/internal/FirebaseInterops.cs index 79ee89bb7..48b096da5 100755 --- a/app/src/internal/FirebaseInterops.cs +++ b/app/src/internal/FirebaseInterops.cs @@ -60,10 +60,9 @@ static FirebaseInterops() private static void LogError(string message) { -//TODO AustinBenoit Must revet this change before merging -//#if FIREBASEAI_DEBUG_LOGGING +#if FIREBASEAI_DEBUG_LOGGING UnityEngine.Debug.LogError(message); -//#endif +#endif } // Cache the methods needed for FirebaseApp reflection. @@ -159,17 +158,14 @@ private static void InitializeAppCheckReflection() try { - UnityEngine.Debug.Log("[FirebaseInterops] Initializing AppCheck Reflection..."); // Set this to false, to allow easy failing out via return. _appCheckReflectionInitialized = false; _appCheckType = Type.GetType(firebaseAppCheckTypeName); if (_appCheckType == null) { - UnityEngine.Debug.Log("[FirebaseInterops] Firebase.AppCheck assembly not found."); return; } - UnityEngine.Debug.Log("[FirebaseInterops] Found Firebase.AppCheck assembly."); // Get the static method GetInstance(FirebaseApp app) _appCheckGetInstanceMethod = _appCheckType.GetMethod( @@ -200,8 +196,6 @@ private static void InitializeAppCheckReflection() LogError($"Could not find {getLimitedUseAppCheckTokenMethodName} method via reflection."); return; } - UnityEngine.Debug.Log("[FirebaseInterops] Found GetLimitedUseAppCheckTokenAsync method."); - // Should be Task Type appCheckTokenTaskType = _appCheckGetTokenMethod.ReturnType; @@ -224,7 +218,6 @@ private static void InitializeAppCheckReflection() return; } - UnityEngine.Debug.Log("[FirebaseInterops] AppCheck Reflection Initialized successfully."); _appCheckReflectionInitialized = true; } catch (Exception e) @@ -236,11 +229,9 @@ private static void InitializeAppCheckReflection() // Gets the AppCheck Token, assuming there is one. Otherwise, returns null. internal static async Task GetAppCheckTokenAsync(FirebaseApp firebaseApp, bool limitedUse = false) { - UnityEngine.Debug.Log($"[FirebaseInterops] GetAppCheckTokenAsync called (limitedUse={limitedUse})"); // If AppCheck reflection failed for any reason, nothing to do. if (!_appCheckReflectionInitialized) { - UnityEngine.Debug.Log("[FirebaseInterops] _appCheckReflectionInitialized is false, returning null."); return null; } @@ -296,7 +287,6 @@ internal static async Task GetAppCheckTokenAsync(FirebaseApp firebaseApp // Get the Token property from the AppCheckToken struct string finalToken = _appCheckTokenTokenProperty.GetValue(tokenResult) as string; - UnityEngine.Debug.Log($"[FirebaseInterops] Successfully fetched App Check token (Length: {(finalToken != null ? finalToken.Length : 0)})"); return finalToken; } catch (Exception e) @@ -450,7 +440,6 @@ internal static async Task AddFirebaseTokensAsync(HttpRequestMessage request, Fi } else if (limitedUseAppCheckTokens) { - UnityEngine.Debug.Log($"[FirebaseInterops] Failed to retrieve limited use token. Token null? {appCheckToken == null}. Reflection Initialized? {_appCheckReflectionInitialized}"); throw new InvalidOperationException("Failed to retrieve Limited Use App Check Token."); } From d7da67747b568bef123c4c064f5c7009b26c29e3 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Tue, 5 May 2026 16:23:45 -0400 Subject: [PATCH 13/19] Try forcing the sdk to load by name --- app/src/internal/FirebaseInterops.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/internal/FirebaseInterops.cs b/app/src/internal/FirebaseInterops.cs index 48b096da5..209fc5336 100755 --- a/app/src/internal/FirebaseInterops.cs +++ b/app/src/internal/FirebaseInterops.cs @@ -163,6 +163,21 @@ private static void InitializeAppCheckReflection() _appCheckType = Type.GetType(firebaseAppCheckTypeName); if (_appCheckType == null) + { + try + { + Assembly appCheckAssembly = Assembly.Load("Firebase.AppCheck"); + if (appCheckAssembly != null) + { + _appCheckType = appCheckAssembly.GetType("Firebase.AppCheck.FirebaseAppCheck"); + } + } + catch (Exception) + { + // Silently ignore if the assembly is truly not present in the project + } + } + if (_appCheckType == null) { return; } From 20814c34fadf86b0db7a6904d524d59884f95eff Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Tue, 5 May 2026 17:21:54 -0400 Subject: [PATCH 14/19] InitalizeAppCheck to happen first --- functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs index eb1c632b9..1c38e9bb7 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs @@ -41,10 +41,10 @@ public class UIHandler : MonoBehaviour { // the required dependencies to use Firebase, and if not, // add them if possible. protected virtual void Start() { + InitializeAppCheck(); FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => { dependencyStatus = task.Result; if (dependencyStatus == DependencyStatus.Available) { - InitializeAppCheck(); InitializeFirebase(); } else { Debug.LogError( From 20b057da70bc466c994561b7615cc9f4fcf079f2 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Wed, 6 May 2026 16:26:02 -0400 Subject: [PATCH 15/19] Clean up the pr for review --- app/src/internal/FirebaseInterops.cs | 19 ------------------- docs/readme.md | 2 +- .../Sample/Functions/UIHandlerAutomated.cs | 2 ++ 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/app/src/internal/FirebaseInterops.cs b/app/src/internal/FirebaseInterops.cs index 209fc5336..bb5ba3a73 100755 --- a/app/src/internal/FirebaseInterops.cs +++ b/app/src/internal/FirebaseInterops.cs @@ -163,21 +163,6 @@ private static void InitializeAppCheckReflection() _appCheckType = Type.GetType(firebaseAppCheckTypeName); if (_appCheckType == null) - { - try - { - Assembly appCheckAssembly = Assembly.Load("Firebase.AppCheck"); - if (appCheckAssembly != null) - { - _appCheckType = appCheckAssembly.GetType("Firebase.AppCheck.FirebaseAppCheck"); - } - } - catch (Exception) - { - // Silently ignore if the assembly is truly not present in the project - } - } - if (_appCheckType == null) { return; } @@ -453,10 +438,6 @@ internal static async Task AddFirebaseTokensAsync(HttpRequestMessage request, Fi { request.Headers.Add(appCheckHeader, appCheckToken); } - else if (limitedUseAppCheckTokens) - { - throw new InvalidOperationException("Failed to retrieve Limited Use App Check Token."); - } string authToken = await GetAuthTokenAsync(firebaseApp); if (!string.IsNullOrEmpty(authToken)) diff --git a/docs/readme.md b/docs/readme.md index d43361454..7d4dc30ab 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -121,7 +121,7 @@ Release Notes - Storage: Added `ListAsync` API to list items and prefixes under a reference. - Functions: Fixed tgz export, added missing asmdef for functions. Fixes issue where Functions were not being exported correctly in the tgz build. - Firebase AI: Fix tgz export, added missing asmdef for Firebase AI. Fixes issue where Firebase AI was not being exported correctly in the tgz build. - - Functions: Added support for passing and enforcing Limited Use App Check tokens. Throws an InvalidOperationException if a required token cannot be fetched. + - Functions: Added support for passing and enforcing Limited Use App Check tokens. ### 13.10.0 - Changes diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs index 8c0bd214f..da13bd5ea 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs @@ -102,6 +102,8 @@ protected override void InitializeAppCheck() { } protected override void InitializeFirebase() { + var app = FirebaseApp.DefaultInstance; + DebugLog(String.Format("Initializing Firebase App: {0} ({1})", app.Name, app.Options.AppId)); // One of the automated tests requires Auth, so we want to sign in before running it. firebaseAuth = Firebase.Auth.FirebaseAuth.DefaultInstance; firebaseAuth.SignInAnonymouslyAsync().ContinueWithOnMainThread( From 813f1fbfc053aacc423a106a5a5d45716903683d Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Fri, 8 May 2026 09:25:20 -0400 Subject: [PATCH 16/19] Fix up comments --- app/src/internal/FirebaseInterops.cs | 5 --- functions/src/HttpsCallableOptions.cs.meta | 7 ++++ .../Firebase/Sample/Functions/UIHandler.cs | 34 +++---------------- .../Sample/Functions/UIHandlerAutomated.cs | 4 +-- 4 files changed, 14 insertions(+), 36 deletions(-) create mode 100644 functions/src/HttpsCallableOptions.cs.meta diff --git a/app/src/internal/FirebaseInterops.cs b/app/src/internal/FirebaseInterops.cs index bb5ba3a73..c880f18fb 100755 --- a/app/src/internal/FirebaseInterops.cs +++ b/app/src/internal/FirebaseInterops.cs @@ -248,11 +248,6 @@ internal static async Task GetAppCheckTokenAsync(FirebaseApp firebaseApp object taskObject; if (limitedUse) { - if (_appCheckGetLimitedUseTokenMethod == null) - { - LogError("Failed to find GetLimitedUseAppCheckTokenAsync method via reflection."); - return null; - } taskObject = _appCheckGetLimitedUseTokenMethod.Invoke(appCheckInstance, null); } else diff --git a/functions/src/HttpsCallableOptions.cs.meta b/functions/src/HttpsCallableOptions.cs.meta new file mode 100644 index 000000000..ead48d8ba --- /dev/null +++ b/functions/src/HttpsCallableOptions.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: adaffa7f07b04ac5aae3cd048ec370ad +timeCreated: 1480838400 +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs index 1c38e9bb7..fea1d2c04 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandler.cs @@ -16,7 +16,7 @@ namespace Firebase.Sample.Functions { using Firebase; using Firebase.Extensions; using Firebase.Functions; - using Firebase.AppCheck; + using System; using System.Collections; using System.Collections.Generic; @@ -35,13 +35,13 @@ public class UIHandler : MonoBehaviour { protected bool UIEnabled = false; private DependencyStatus dependencyStatus = DependencyStatus.UnavailableOther; protected FirebaseFunctions functions; - private string appCheckDebugToken = "REPLACE_WITH_APP_CHECK_TOKEN"; + // When the app starts, check to make sure that we have // the required dependencies to use Firebase, and if not, // add them if possible. protected virtual void Start() { - InitializeAppCheck(); + FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => { dependencyStatus = task.Result; if (dependencyStatus == DependencyStatus.Available) { @@ -53,10 +53,7 @@ protected virtual void Start() { }); } - protected virtual void InitializeAppCheck() { - DebugAppCheckProviderFactory.Instance.SetDebugToken(appCheckDebugToken); - FirebaseAppCheck.SetAppCheckProviderFactory(DebugAppCheckProviderFactory.Instance); - } + protected virtual void InitializeFirebase() { functions = FirebaseFunctions.DefaultInstance; @@ -101,9 +98,7 @@ protected void GUIDisplayTests() { if (GUILayout.Button("addNumbers")) { StartCoroutine(AddNumbers(5, 7)); } - if (GUILayout.Button("addNumbers (Limited Use)")) { - StartCoroutine(AddNumbersLimitedUse(5, 7)); - } + GUILayout.EndVertical(); } @@ -128,26 +123,7 @@ protected IEnumerator AddNumbers(int firstNumber, int secondNumber) { yield return new WaitUntil(() => task.IsCompleted); } - protected IEnumerator AddNumbersLimitedUse(int firstNumber, int secondNumber) { - var func = functions.GetHttpsCallable("addNumbers", new HttpsCallableOptions { LimitedUseAppCheckTokens = true }); - var data = new Dictionary(); - data["firstNumber"] = firstNumber; - data["secondNumber"] = secondNumber; - var task = func.CallAsync(data).ContinueWithOnMainThread((callTask) => { - if (callTask.IsFaulted) { - // The function unexpectedly failed. - DebugLog("FAILED!"); - DebugLog(String.Format(" Error: {0}", callTask.Exception)); - return; - } - - // The function succeeded. - var result = (IDictionary)callTask.Result.Data; - DebugLog(String.Format("AddNumbers (Limited Use): {0}", result["operationResult"])); - }); - yield return new WaitUntil(() => task.IsCompleted); - } // Render the buttons and other controls. void GUIDisplayControls() { diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs index da13bd5ea..2cdd0374f 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs @@ -79,6 +79,7 @@ public static IEnumerable AllTests() { } protected override void Start() { + InitializeAppCheck(); // Set the list of tests to run, note this is done at Start since they are // non-static. var testCases = AllTests().ToArray(); @@ -92,6 +93,7 @@ protected override void Start() { ); UIEnabled = false; + base.Start(); } @@ -102,8 +104,6 @@ protected override void InitializeAppCheck() { } protected override void InitializeFirebase() { - var app = FirebaseApp.DefaultInstance; - DebugLog(String.Format("Initializing Firebase App: {0} ({1})", app.Name, app.Options.AppId)); // One of the automated tests requires Auth, so we want to sign in before running it. firebaseAuth = Firebase.Auth.FirebaseAuth.DefaultInstance; firebaseAuth.SignInAnonymouslyAsync().ContinueWithOnMainThread( From 1b19053c7cd1cc05946bacd3210dde4f65db42bd Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Fri, 8 May 2026 14:21:00 -0400 Subject: [PATCH 17/19] Update restore_secrets.py to not patch UIHandler.cs in functions --- scripts/gha/restore_secrets.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/gha/restore_secrets.py b/scripts/gha/restore_secrets.py index 1b21d98bc..7ddd0fb65 100644 --- a/scripts/gha/restore_secrets.py +++ b/scripts/gha/restore_secrets.py @@ -131,8 +131,7 @@ def main(argv): _patch_file(file_path, "REPLACE_WITH_APP_CHECK_TOKEN", debug_token) file_path = os.path.join(repo_dir, "functions", "testapp", "Assets", "Firebase", "Sample", CAPITALIZATIONS["functions"], "UIHandlerAutomated.cs") _patch_file(file_path, "REPLACE_WITH_APP_CHECK_TOKEN", debug_token) - file_path = os.path.join(repo_dir, "functions", "testapp", "Assets", "Firebase", "Sample", CAPITALIZATIONS["functions"], "UIHandler.cs") - _patch_file(file_path, "REPLACE_WITH_APP_CHECK_TOKEN", debug_token) + print("Attempting to decrypt GCS service account key file.") decrypted_key_file = os.path.join(secrets_dir, "gcs_key_file.json") From a5210ec6b9aeae18858cb64b5287e9221e75d85a Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Fri, 8 May 2026 14:38:26 -0400 Subject: [PATCH 18/19] Fix compilation error in UIHandlerAutomated.cs by removing override and calling InitializeAppCheck --- .../Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs index 2cdd0374f..a2e17d428 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs @@ -93,11 +93,11 @@ protected override void Start() { ); UIEnabled = false; - + InitializeAppCheck(); base.Start(); } - protected override void InitializeAppCheck() { + protected void InitializeAppCheck() { DebugLog("Initializing App Check directly in automated handler"); DebugAppCheckProviderFactory.Instance.SetDebugToken(appCheckDebugTokenForAutomated); FirebaseAppCheck.SetAppCheckProviderFactory(DebugAppCheckProviderFactory.Instance); From be211d176965eb6a486721bd0c803323c8988d20 Mon Sep 17 00:00:00 2001 From: AustinBenoit Date: Fri, 8 May 2026 14:54:16 -0400 Subject: [PATCH 19/19] Remove duplicated call to enable --- .../Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs index a2e17d428..08f4fcfc5 100644 --- a/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs +++ b/functions/testapp/Assets/Firebase/Sample/Functions/UIHandlerAutomated.cs @@ -93,7 +93,7 @@ protected override void Start() { ); UIEnabled = false; - InitializeAppCheck(); + base.Start(); }