From 29eab296ef775a86b9fa5c53741ffc553b51f5d2 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Fri, 12 Dec 2025 10:40:02 -0500 Subject: [PATCH 01/10] Feat: Add limited use token suport --- app_check/src/android/app_check_android.cc | 41 ++++++++++++++++++- app_check/src/common/app_check.cc | 9 ++++ .../src/desktop/debug_provider_desktop.cc | 23 +++++++++++ app_check/src/desktop/debug_token_request.fbs | 1 + app_check/src/desktop/debug_token_request.h | 5 +++ app_check/src/include/firebase/app_check.h | 10 +++++ app_check/src/ios/app_check_ios.mm | 13 ++++++ .../internal/cpp/JniAppCheckProvider.java | 15 +++++++ 8 files changed, 116 insertions(+), 1 deletion(-) diff --git a/app_check/src/android/app_check_android.cc b/app_check/src/android/app_check_android.cc index f7295d525a..86e5c553dd 100644 --- a/app_check/src/android/app_check_android.cc +++ b/app_check/src/android/app_check_android.cc @@ -109,11 +109,17 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken( JNIEnv* env, jobject j_provider, jlong c_provider, jobject task_completion_source); +JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetLimitedUseToken( + JNIEnv* env, jobject j_provider, jlong c_provider, + jobject task_completion_source); + static const JNINativeMethod kNativeJniAppCheckProviderMethods[] = { {"nativeGetToken", "(JLcom/google/android/gms/tasks/TaskCompletionSource;)V", reinterpret_cast(JniAppCheckProvider_nativeGetToken)}, -}; + {"nativeGetLimitedUseToken", + "(JLcom/google/android/gms/tasks/TaskCompletionSource;)V", + reinterpret_cast(JniAppCheckProvider_nativeGetLimitedUseToken)}}; // clang-format off #define JNI_APP_CHECK_LISTENER_METHODS(X) \ @@ -266,6 +272,39 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken( provider->GetToken(token_callback); } +JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetLimitedUseToken( + JNIEnv* env, jobject j_provider, jlong c_provider, + jobject task_completion_source) { + // Create GlobalReferences to the provider and task. These references will be + // deleted in the completion callback. + jobject j_provider_global = env->NewGlobalRef(j_provider); + jobject task_completion_source_global = + env->NewGlobalRef(task_completion_source); + + // Defines a C++ callback method to call + // JniAppCheckProvider.HandleGetTokenResult with the resulting token + auto token_callback{[j_provider_global, task_completion_source_global]( + firebase::app_check::AppCheckToken token, + int error_code, const std::string& error_message) { + // util::GetJNIEnvFromApp returns a threadsafe instance of JNIEnv. + JNIEnv* env = firebase::util::GetJNIEnvFromApp(); + jstring error_string = env->NewStringUTF(error_message.c_str()); + jstring token_string = env->NewStringUTF(token.token.c_str()); + env->CallVoidMethod( + j_provider_global, + jni_provider::GetMethodId(jni_provider::kHandleGetTokenResult), + task_completion_source_global, token_string, token.expire_time_millis, + error_code, error_string); + FIREBASE_ASSERT(!util::CheckAndClearJniExceptions(env)); + env->DeleteLocalRef(token_string); + env->DeleteLocalRef(error_string); + env->DeleteGlobalRef(j_provider_global); + env->DeleteGlobalRef(task_completion_source_global); + }}; + AppCheckProvider* provider = reinterpret_cast(c_provider); + provider->GetLimitedUseToken(token_callback); +} + JNIEXPORT void JNICALL JniAppCheckListener_nativeOnAppCheckTokenChanged( JNIEnv* env, jobject clazz, jlong c_app_check, jobject token) { auto app_check_internal = reinterpret_cast(c_app_check); diff --git a/app_check/src/common/app_check.cc b/app_check/src/common/app_check.cc index 057b8985b4..7c4a25d360 100644 --- a/app_check/src/common/app_check.cc +++ b/app_check/src/common/app_check.cc @@ -67,6 +67,15 @@ static std::map<::firebase::App*, AppCheck*>* g_app_check_map = nullptr; // Define the destructors for the virtual listener/provider/factory classes. AppCheckListener::~AppCheckListener() {} AppCheckProvider::~AppCheckProvider() {} +void AppCheckProvider::GetLimitedUseToken( + std::function + completion_callback) { + LogWarning( + "A limited-use token was requested, but the custom provider did not " + "implement the GetLimitedUseToken method. The default implementation is " + "triggered as a result, and GetToken has been invoked instead."); + GetToken(completion_callback); +} AppCheckProviderFactory::~AppCheckProviderFactory() {} namespace internal { diff --git a/app_check/src/desktop/debug_provider_desktop.cc b/app_check/src/desktop/debug_provider_desktop.cc index 26754ede26..e0e558b47a 100644 --- a/app_check/src/desktop/debug_provider_desktop.cc +++ b/app_check/src/desktop/debug_provider_desktop.cc @@ -41,7 +41,16 @@ class DebugAppCheckProvider : public AppCheckProvider { void GetToken(std::function completion_callback) override; + void GetLimitedUseToken( + std::function + completion_callback) override; + private: + void GetTokenInternal( + bool limited_use, + std::function + completion_callback); + App* app_; scheduler::Scheduler scheduler_; @@ -92,6 +101,19 @@ void GetTokenAsync(std::shared_ptr request, void DebugAppCheckProvider::GetToken( std::function completion_callback) { + GetTokenInternal(false, completion_callback); +} + +void DebugAppCheckProvider::GetLimitedUseToken( + std::function + completion_callback) { + GetTokenInternal(true, completion_callback); +} + +void DebugAppCheckProvider::GetTokenInternal( + bool limited_use, + std::function + completion_callback) { // Identify the user's debug token const char* debug_token_cstr; if (!debug_token_.empty()) { @@ -109,6 +131,7 @@ void DebugAppCheckProvider::GetToken( // Exchange debug token with the backend to get a proper attestation token. auto request = std::make_shared(app_); request->SetDebugToken(debug_token_cstr); + request->SetLimitedUse(limited_use); // Use an async call, since we don't want to block on the server response. auto async_call = diff --git a/app_check/src/desktop/debug_token_request.fbs b/app_check/src/desktop/debug_token_request.fbs index 65b2413757..8bcaca2dba 100644 --- a/app_check/src/desktop/debug_token_request.fbs +++ b/app_check/src/desktop/debug_token_request.fbs @@ -16,6 +16,7 @@ namespace firebase.app_check.fbs; table DebugTokenRequest { debug_token:string; + limited_use:bool; } root_type DebugTokenRequest; diff --git a/app_check/src/desktop/debug_token_request.h b/app_check/src/desktop/debug_token_request.h index b2ea2b5cf0..6688e90761 100644 --- a/app_check/src/desktop/debug_token_request.h +++ b/app_check/src/desktop/debug_token_request.h @@ -55,6 +55,11 @@ class DebugTokenRequest application_data_->debug_token = std::move(debug_token); UpdatePostFields(); } + + void SetLimitedUse(bool limited_use) { + application_data_->limited_use = limited_use; + UpdatePostFields(); + } }; } // namespace internal diff --git a/app_check/src/include/firebase/app_check.h b/app_check/src/include/firebase/app_check.h index 19167bcf3b..7073e4861b 100644 --- a/app_check/src/include/firebase/app_check.h +++ b/app_check/src/include/firebase/app_check.h @@ -78,6 +78,16 @@ class AppCheckProvider { virtual void GetToken( std::function completion_callback) = 0; + + /// Fetches an AppCheckToken suitable for consumption in limited-use scenarios + /// and then calls the provided callback function with the token or with an + /// error code and error message. + /// + /// If you do not implement this method, the default implementation invokes + /// the GetToken method whenever a limited-use token is requested. + virtual void GetLimitedUseToken( + std::function + completion_callback); }; /// Interface for a factory that generates {@link AppCheckProvider}s. diff --git a/app_check/src/ios/app_check_ios.mm b/app_check/src/ios/app_check_ios.mm index 1b6d37fd11..307c6b33d5 100644 --- a/app_check/src/ios/app_check_ios.mm +++ b/app_check/src/ios/app_check_ios.mm @@ -59,6 +59,19 @@ - (void)getTokenWithCompletion:(nonnull void (^)(FIRAppCheckToken* _Nullable, _cppProvider->GetToken(token_callback); } +- (void)getLimitedUseTokenWithCompletion:(nonnull void (^)(FIRAppCheckToken* _Nullable, + NSError* _Nullable))handler { + auto token_callback{[handler](firebase::app_check::AppCheckToken token, int error_code, + const std::string& error_message) { + NSError* ios_error = firebase::app_check::internal::AppCheckErrorToNSError( + static_cast(error_code), error_message); + FIRAppCheckToken* ios_token = + firebase::app_check::internal::AppCheckTokenToFIRAppCheckToken(token); + handler(ios_token, ios_error); + }}; + _cppProvider->GetLimitedUseToken(token_callback); +} + @end // Defines an iOS AppCheckProviderFactory that wraps a given C++ Factory. diff --git a/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java b/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java index 365c61c823..43acd2e08a 100644 --- a/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java +++ b/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java @@ -41,6 +41,15 @@ public Task getToken() { return taskCompletionSource.getTask(); } + public Task getLimitedUseToken() { + TaskCompletionSource taskCompletionSource = + new TaskCompletionSource(); + // Call the C++ provider to get an AppCheckToken and set the task result. + // The C++ code will call handleGetTokenResult with the resulting token. + nativeGetLimitedUseToken(cProvider, taskCompletionSource); + return taskCompletionSource.getTask(); + } + /** * Called by C++ with a token in order to complete the java task. */ @@ -58,4 +67,10 @@ public void handleGetTokenResult(TaskCompletionSource taskComplet */ private native void nativeGetToken( long cProvider, TaskCompletionSource task_completion_source); + + /** + * This function is implemented in the AppCheck C++ library (app_check_android.cc). + */ + private native void nativeGetLimitedUseToken( + long cProvider, TaskCompletionSource task_completion_source); } From a3c5f4260f66ab07045dc87a54bd24ea4ed7cea4 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Tue, 16 Dec 2025 15:37:38 -0500 Subject: [PATCH 02/10] Create an intergration test for the limited use tokens --- .../integration_test/src/integration_test.cc | 26 +++++++++++++++++++ app_check/src/android/app_check_android.cc | 5 ---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app_check/integration_test/src/integration_test.cc b/app_check/integration_test/src/integration_test.cc index d0cc4ee8cd..321e71ff69 100644 --- a/app_check/integration_test/src/integration_test.cc +++ b/app_check/integration_test/src/integration_test.cc @@ -588,6 +588,32 @@ TEST_F(FirebaseAppCheckTest, TestDebugProviderValidToken) { got_token_future.wait_for(kGetTokenTimeout)); } +TEST_F(FirebaseAppCheckTest, TestDebugProviderValidLimitedUseToken) { + firebase::app_check::DebugAppCheckProviderFactory* factory = + firebase::app_check::DebugAppCheckProviderFactory::GetInstance(); + ASSERT_NE(factory, nullptr); + InitializeAppCheckWithDebug(); + InitializeApp(); + + firebase::app_check::AppCheckProvider* provider = + factory->CreateProvider(app_); + ASSERT_NE(provider, nullptr); + auto got_token_promise = std::make_shared>(); + auto token_callback{ + [got_token_promise](firebase::app_check::AppCheckToken token, + int error_code, const std::string& error_message) { + EXPECT_EQ(firebase::app_check::kAppCheckErrorNone, error_code); + EXPECT_EQ("", error_message); + EXPECT_NE(0, token.expire_time_millis); + EXPECT_NE("", token.token); + got_token_promise->set_value(); + }}; + provider->GetLimitedUseToken(token_callback); + auto got_token_future = got_token_promise->get_future(); + ASSERT_EQ(std::future_status::ready, + got_token_future.wait_for(kGetTokenTimeout)); +} + TEST_F(FirebaseAppCheckTest, TestAppAttestProvider) { firebase::app_check::AppAttestProviderFactory* factory = firebase::app_check::AppAttestProviderFactory::GetInstance(); diff --git a/app_check/src/android/app_check_android.cc b/app_check/src/android/app_check_android.cc index 86e5c553dd..e76455db57 100644 --- a/app_check/src/android/app_check_android.cc +++ b/app_check/src/android/app_check_android.cc @@ -242,18 +242,13 @@ JNIEXPORT jlong JNICALL JniAppCheckProviderFactory_nativeCreateProvider( JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken( JNIEnv* env, jobject j_provider, jlong c_provider, jobject task_completion_source) { - // Create GlobalReferences to the provider and task. These references will be - // deleted in the completion callback. jobject j_provider_global = env->NewGlobalRef(j_provider); jobject task_completion_source_global = env->NewGlobalRef(task_completion_source); - // Defines a C++ callback method to call - // JniAppCheckProvider.HandleGetTokenResult with the resulting token auto token_callback{[j_provider_global, task_completion_source_global]( firebase::app_check::AppCheckToken token, int error_code, const std::string& error_message) { - // util::GetJNIEnvFromApp returns a threadsafe instance of JNIEnv. JNIEnv* env = firebase::util::GetJNIEnvFromApp(); jstring error_string = env->NewStringUTF(error_message.c_str()); jstring token_string = env->NewStringUTF(token.token.c_str()); From c54f32c21887529454e22939f9c314adad6b2106 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Tue, 6 Jan 2026 17:15:59 -0500 Subject: [PATCH 03/10] Add in release notes --- release_build_files/readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 40b2970e84..bebceabca9 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -613,6 +613,10 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes +### Upcoming +- Changes + - App Check: Add in support for Limited Use Tokens. + ### 13.4.0 - Changes - General (Android): Update to Firebase Android BoM version 34.8.0. From 5deebd726b72ded806ccfd224b71cca95d72c088 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Tue, 13 Jan 2026 12:58:58 -0500 Subject: [PATCH 04/10] Fix comments on get limted use tokens --- app_check/src/common/app_check.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app_check/src/common/app_check.cc b/app_check/src/common/app_check.cc index 7c4a25d360..a2713f5463 100644 --- a/app_check/src/common/app_check.cc +++ b/app_check/src/common/app_check.cc @@ -67,6 +67,9 @@ static std::map<::firebase::App*, AppCheck*>* g_app_check_map = nullptr; // Define the destructors for the virtual listener/provider/factory classes. AppCheckListener::~AppCheckListener() {} AppCheckProvider::~AppCheckProvider() {} + +// Default implimenation. Can be overriden by App check providers that support +// limited use tokens void AppCheckProvider::GetLimitedUseToken( std::function completion_callback) { From 0461c1944bbea5676ae94b66bd159571420b791b Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Tue, 20 Jan 2026 13:20:24 -0500 Subject: [PATCH 05/10] Flush out the implementation for limited use tokens --- .../integration_test/src/integration_test.cc | 21 +++++++++++ app_check/src/android/app_check_android.cc | 29 +++++++++++++++ app_check/src/android/app_check_android.h | 4 +++ app_check/src/android/common_android.cc | 24 +++++++++++++ app_check/src/android/common_android.h | 9 +++++ app_check/src/common/app_check.cc | 10 ++++++ app_check/src/common/common.h | 1 + app_check/src/desktop/app_check_desktop.cc | 30 ++++++++++++++++ app_check/src/desktop/app_check_desktop.h | 4 +++ app_check/src/include/firebase/app_check.h | 8 +++++ app_check/src/ios/app_attest_provider_ios.mm | 21 +++++++++++ app_check/src/ios/app_check_ios.h | 4 +++ app_check/src/ios/app_check_ios.mm | 36 +++++++++++++++++++ app_check/src/ios/debug_provider_ios.mm | 21 +++++++++++ .../src/ios/device_check_provider_ios.mm | 21 +++++++++++ 15 files changed, 243 insertions(+) diff --git a/app_check/integration_test/src/integration_test.cc b/app_check/integration_test/src/integration_test.cc index 321e71ff69..5b14374917 100644 --- a/app_check/integration_test/src/integration_test.cc +++ b/app_check/integration_test/src/integration_test.cc @@ -516,6 +516,27 @@ TEST_F(FirebaseAppCheckTest, TestGetTokenLastResult) { future2.result()->expire_time_millis); } +TEST_F(FirebaseAppCheckTest, TestGetLimitedUseAppCheckToken) { + InitializeAppCheckWithDebug(); + InitializeApp(); + ::firebase::app_check::AppCheck* app_check = + ::firebase::app_check::AppCheck::GetInstance(app_); + ASSERT_NE(app_check, nullptr); + + firebase::Future<::firebase::app_check::AppCheckToken> future = + app_check->GetLimitedUseAppCheckToken(); + EXPECT_TRUE(WaitForCompletion(future, "GetLimitedUseAppCheckToken")); + ::firebase::app_check::AppCheckToken token = *future.result(); + EXPECT_NE(token.token, ""); + EXPECT_NE(token.expire_time_millis, 0); + + firebase::Future<::firebase::app_check::AppCheckToken> future2 = + app_check->GetLimitedUseAppCheckTokenLastResult(); + EXPECT_TRUE(WaitForCompletion(future2, "GetLimitedUseAppCheckTokenLastResult")); + EXPECT_EQ(future.result()->expire_time_millis, + future2.result()->expire_time_millis); +} + TEST_F(FirebaseAppCheckTest, TestAddTokenChangedListener) { InitializeAppCheckWithDebug(); InitializeApp(); diff --git a/app_check/src/android/app_check_android.cc b/app_check/src/android/app_check_android.cc index e76455db57..567de8869c 100644 --- a/app_check/src/android/app_check_android.cc +++ b/app_check/src/android/app_check_android.cc @@ -47,6 +47,8 @@ namespace internal { "(Z)V"), \ X(GetToken, "getAppCheckToken", \ "(Z)Lcom/google/android/gms/tasks/Task;"), \ + X(GetLimitedUseToken, "getLimitedUseAppCheckToken", \ + "()Lcom/google/android/gms/tasks/Task;"), \ X(AddAppCheckListener, "addAppCheckListener", \ "(Lcom/google/firebase/appcheck/FirebaseAppCheck$AppCheckListener;)V"), \ X(RemoveAppCheckListener, "removeAppCheckListener", \ @@ -486,6 +488,33 @@ Future AppCheckInternal::GetAppCheckTokenLastResult() { future()->LastResult(kAppCheckFnGetAppCheckToken)); } +Future AppCheckInternal::GetLimitedUseAppCheckToken() { + JNIEnv* env = app_->GetJNIEnv(); + auto handle = + future()->SafeAlloc(kAppCheckFnGetLimitedUseAppCheckToken); + jobject j_task = env->CallObjectMethod( + app_check_impl_, app_check::GetMethodId(app_check::kGetLimitedUseToken)); + + std::string error = util::GetAndClearExceptionMessage(env); + if (error.empty()) { + auto data_handle = new FutureDataHandle(future(), handle); + util::RegisterCallbackOnTask(env, j_task, TokenResultCallback, + reinterpret_cast(data_handle), + jni_task_id_.c_str()); + } else { + AppCheckToken empty_token; + future()->CompleteWithResult(handle, kAppCheckErrorUnknown, error.c_str(), + empty_token); + } + env->DeleteLocalRef(j_task); + return MakeFuture(future(), handle); +} + +Future AppCheckInternal::GetLimitedUseAppCheckTokenLastResult() { + return static_cast&>( + future()->LastResult(kAppCheckFnGetLimitedUseAppCheckToken)); +} + void AppCheckInternal::AddAppCheckListener(AppCheckListener* listener) { MutexLock lock(listeners_mutex_); auto it = std::find(listeners_.begin(), listeners_.end(), listener); diff --git a/app_check/src/android/app_check_android.h b/app_check/src/android/app_check_android.h index 22c30259a1..4ca71c4122 100644 --- a/app_check/src/android/app_check_android.h +++ b/app_check/src/android/app_check_android.h @@ -45,6 +45,10 @@ class AppCheckInternal { Future GetAppCheckTokenLastResult(); + Future GetLimitedUseAppCheckToken(); + + Future GetLimitedUseAppCheckTokenLastResult(); + void AddAppCheckListener(AppCheckListener* listener); void RemoveAppCheckListener(AppCheckListener* listener); diff --git a/app_check/src/android/common_android.cc b/app_check/src/android/common_android.cc index 2bae6cf878..1ea48b6d0b 100644 --- a/app_check/src/android/common_android.cc +++ b/app_check/src/android/common_android.cc @@ -161,6 +161,30 @@ void AndroidAppCheckProvider::GetToken( env->DeleteLocalRef(j_task); } +void AndroidAppCheckProvider::GetLimitedUseToken( + std::function + completion_callback) { + JNIEnv* env = GetJniEnv(); + + jobject j_task = env->CallObjectMethod( + android_provider_, app_check_provider::GetMethodId( + app_check_provider::kGetLimitedUseToken)); + std::string error = util::GetAndClearExceptionMessage(env); + if (error.empty()) { + // Create an object to wrap the callback function + TokenResultCallbackData* completion_callback_data = + new TokenResultCallbackData(completion_callback); + util::RegisterCallbackOnTask( + env, j_task, TokenResultCallback, + reinterpret_cast(completion_callback_data), + jni_task_id_.c_str()); + } else { + AppCheckToken empty_token; + completion_callback(empty_token, kAppCheckErrorUnknown, error.c_str()); + } + env->DeleteLocalRef(j_task); +} + } // namespace internal } // namespace app_check } // namespace firebase diff --git a/app_check/src/android/common_android.h b/app_check/src/android/common_android.h index bd8ca8ec4a..1daf9fdeb0 100644 --- a/app_check/src/android/common_android.h +++ b/app_check/src/android/common_android.h @@ -33,6 +33,8 @@ namespace internal { // clang-format off #define APP_CHECK_PROVIDER_METHODS(X) \ X(GetToken, "getToken", \ + "()Lcom/google/android/gms/tasks/Task;"), \ + X(GetLimitedUseToken, "getLimitedUseToken", \ "()Lcom/google/android/gms/tasks/Task;") // clang-format on @@ -59,6 +61,13 @@ class AndroidAppCheckProvider : public AppCheckProvider { void GetToken(std::function completion_callback) override; + /// Fetches an AppCheckToken suitable for consumption in limited-use scenarios + /// and then calls the provided callback function with the token or with an + /// error code and error message. + void GetLimitedUseToken( + std::function + completion_callback) override; + private: jobject android_provider_; diff --git a/app_check/src/common/app_check.cc b/app_check/src/common/app_check.cc index a2713f5463..5a74207950 100644 --- a/app_check/src/common/app_check.cc +++ b/app_check/src/common/app_check.cc @@ -161,6 +161,16 @@ Future AppCheck::GetAppCheckTokenLastResult() { : Future(); } +Future AppCheck::GetLimitedUseAppCheckToken() { + return internal_ ? internal_->GetLimitedUseAppCheckToken() + : Future(); +} + +Future AppCheck::GetLimitedUseAppCheckTokenLastResult() { + return internal_ ? internal_->GetLimitedUseAppCheckTokenLastResult() + : Future(); +} + void AppCheck::AddAppCheckListener(AppCheckListener* listener) { if (!internal_) return; internal_->AddAppCheckListener(listener); diff --git a/app_check/src/common/common.h b/app_check/src/common/common.h index d5bc500587..0303b12a75 100644 --- a/app_check/src/common/common.h +++ b/app_check/src/common/common.h @@ -23,6 +23,7 @@ namespace internal { enum AppCheckFn { kAppCheckFnGetAppCheckToken = 0, kAppCheckFnGetAppCheckStringInternal, + kAppCheckFnGetLimitedUseAppCheckToken, kAppCheckFnCount, }; diff --git a/app_check/src/desktop/app_check_desktop.cc b/app_check/src/desktop/app_check_desktop.cc index 9fc7cf48ea..f521a8131b 100644 --- a/app_check/src/desktop/app_check_desktop.cc +++ b/app_check/src/desktop/app_check_desktop.cc @@ -120,6 +120,36 @@ Future AppCheckInternal::GetAppCheckTokenLastResult() { future()->LastResult(kAppCheckFnGetAppCheckToken)); } +Future AppCheckInternal::GetLimitedUseAppCheckToken() { + auto handle = + future()->SafeAlloc(kAppCheckFnGetLimitedUseAppCheckToken); + // Get a new token, and pass the result into the future. + AppCheckProvider* provider = GetProvider(); + if (provider != nullptr) { + auto token_callback{ + [this, handle](firebase::app_check::AppCheckToken token, int error_code, + const std::string& error_message) { + if (error_code == firebase::app_check::kAppCheckErrorNone) { + // Note that we do NOT update the cached token for limited-use tokens. + future()->CompleteWithResult(handle, 0, token); + } else { + future()->Complete(handle, error_code, error_message.c_str()); + } + }}; + provider->GetLimitedUseToken(token_callback); + } else { + future()->Complete( + handle, firebase::app_check::kAppCheckErrorInvalidConfiguration, + "No AppCheckProvider installed."); + } + return MakeFuture(future(), handle); +} + +Future AppCheckInternal::GetLimitedUseAppCheckTokenLastResult() { + return static_cast&>( + future()->LastResult(kAppCheckFnGetLimitedUseAppCheckToken)); +} + Future AppCheckInternal::GetAppCheckTokenStringInternal() { auto handle = future()->SafeAlloc(kAppCheckFnGetAppCheckStringInternal); diff --git a/app_check/src/desktop/app_check_desktop.h b/app_check/src/desktop/app_check_desktop.h index 87f6d813f7..cdd3d21358 100644 --- a/app_check/src/desktop/app_check_desktop.h +++ b/app_check/src/desktop/app_check_desktop.h @@ -62,6 +62,10 @@ class AppCheckInternal { Future GetAppCheckTokenLastResult(); + Future GetLimitedUseAppCheckToken(); + + Future GetLimitedUseAppCheckTokenLastResult(); + // Gets the App Check token as just the string, to be used by // internal methods to not conflict with the publicly returned future. Future GetAppCheckTokenStringInternal(); diff --git a/app_check/src/include/firebase/app_check.h b/app_check/src/include/firebase/app_check.h index 7073e4861b..1f74288630 100644 --- a/app_check/src/include/firebase/app_check.h +++ b/app_check/src/include/firebase/app_check.h @@ -153,6 +153,14 @@ class AppCheck { /// Returns the result of the most recent call to GetAppCheckToken(); Future GetAppCheckTokenLastResult(); + /// Requests a limited-use Firebase App Check token. This method should be used + /// ONLY if you need to authorize requests to a non-Firebase backend. Requests + /// to Firebase backends are authorized automatically if configured. + Future GetLimitedUseAppCheckToken(); + + /// Returns the result of the most recent call to GetLimitedUseAppCheckToken(); + Future GetLimitedUseAppCheckTokenLastResult(); + /// Registers an {@link AppCheckListener} to changes in the token state. This /// method should be used ONLY if you need to authorize requests to a /// non-Firebase backend. Requests to Firebase backends are authorized diff --git a/app_check/src/ios/app_attest_provider_ios.mm b/app_check/src/ios/app_attest_provider_ios.mm index 21e8498f4e..b2a991c68d 100644 --- a/app_check/src/ios/app_attest_provider_ios.mm +++ b/app_check/src/ios/app_attest_provider_ios.mm @@ -37,6 +37,13 @@ virtual void GetToken( std::function completion_callback) override; + /// Fetches an AppCheckToken suitable for consumption in limited-use scenarios + /// and then calls the provided callback function with the token or with an + /// error code and error message. + virtual void GetLimitedUseToken( + std::function + completion_callback) override; + private: FIRAppAttestProvider* ios_provider_; }; @@ -55,6 +62,20 @@ virtual void GetToken( }]; } +void AppAttestProvider::GetLimitedUseToken( + std::function + completion_callback) { + [ios_provider_ + getLimitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, + NSError* _Nullable error) { + completion_callback( + firebase::app_check::internal::AppCheckTokenFromFIRAppCheckToken( + token), + firebase::app_check::internal::AppCheckErrorFromNSError(error), + util::NSStringToString(error.localizedDescription).c_str()); + }]; +} + AppAttestProviderFactoryInternal::AppAttestProviderFactoryInternal() : created_providers_() {} AppAttestProviderFactoryInternal::~AppAttestProviderFactoryInternal() { diff --git a/app_check/src/ios/app_check_ios.h b/app_check/src/ios/app_check_ios.h index bc68aea3c1..003ab2ae08 100644 --- a/app_check/src/ios/app_check_ios.h +++ b/app_check/src/ios/app_check_ios.h @@ -72,6 +72,10 @@ class AppCheckInternal { Future GetAppCheckTokenLastResult(); + Future GetLimitedUseAppCheckToken(); + + Future GetLimitedUseAppCheckTokenLastResult(); + void AddAppCheckListener(AppCheckListener* listener); void RemoveAppCheckListener(AppCheckListener* listener); diff --git a/app_check/src/ios/app_check_ios.mm b/app_check/src/ios/app_check_ios.mm index 307c6b33d5..ef2bc8e48e 100644 --- a/app_check/src/ios/app_check_ios.mm +++ b/app_check/src/ios/app_check_ios.mm @@ -222,6 +222,42 @@ - (void)appCheckTokenDidChangeNotification:(NSNotification*)notification { future()->LastResult(kAppCheckFnGetAppCheckToken)); } +Future AppCheckInternal::GetLimitedUseAppCheckToken() { + __block SafeFutureHandle handle = + future()->SafeAlloc(kAppCheckFnGetLimitedUseAppCheckToken); + + [impl() limitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, + NSError* _Nullable error) { + AppCheckToken cpp_token = AppCheckTokenFromFIRAppCheckToken(token); + if (error != nil) { + NSLog(@"Unable to retrieve limited-use App Check token: %@", error); + int error_code = + firebase::app_check::internal::AppCheckErrorFromNSError(error); + std::string error_message = + util::NSStringToString(error.localizedDescription); + + future()->CompleteWithResult(handle, error_code, error_message.c_str(), + cpp_token); + return; + } + if (token == nil) { + NSLog(@"App Check token is nil."); + future()->CompleteWithResult(handle, kAppCheckErrorUnknown, + "AppCheck GetLimitedUseToken returned an " + "empty token.", + cpp_token); + return; + } + future()->CompleteWithResult(handle, kAppCheckErrorNone, cpp_token); + }]; + return MakeFuture(future(), handle); +} + +Future AppCheckInternal::GetLimitedUseAppCheckTokenLastResult() { + return static_cast&>( + future()->LastResult(kAppCheckFnGetLimitedUseAppCheckToken)); +} + void AppCheckInternal::AddAppCheckListener(AppCheckListener* listener) { [notification_center_wrapper() addListener:listener]; } diff --git a/app_check/src/ios/debug_provider_ios.mm b/app_check/src/ios/debug_provider_ios.mm index bcbb289cc2..8f2538c733 100644 --- a/app_check/src/ios/debug_provider_ios.mm +++ b/app_check/src/ios/debug_provider_ios.mm @@ -42,6 +42,13 @@ virtual void GetToken( std::function completion_callback) override; + /// Fetches an AppCheckToken suitable for consumption in limited-use scenarios + /// and then calls the provided callback function with the token or with an + /// error code and error message. + virtual void GetLimitedUseToken( + std::function + completion_callback) override; + private: FIRAppCheckDebugProvider* ios_provider_; }; @@ -61,6 +68,20 @@ virtual void GetToken( }]; } +void DebugAppCheckProvider::GetLimitedUseToken( + std::function + completion_callback) { + [ios_provider_ + getLimitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, + NSError* _Nullable error) { + completion_callback( + firebase::app_check::internal::AppCheckTokenFromFIRAppCheckToken( + token), + firebase::app_check::internal::AppCheckErrorFromNSError(error), + util::NSStringToString(error.localizedDescription).c_str()); + }]; +} + DebugAppCheckProviderFactoryInternal::DebugAppCheckProviderFactoryInternal() : created_providers_() { ios_provider_factory_ = std::make_unique( diff --git a/app_check/src/ios/device_check_provider_ios.mm b/app_check/src/ios/device_check_provider_ios.mm index b5e0677f9f..65a86c34eb 100644 --- a/app_check/src/ios/device_check_provider_ios.mm +++ b/app_check/src/ios/device_check_provider_ios.mm @@ -38,6 +38,13 @@ virtual void GetToken( std::function completion_callback) override; + /// Fetches an AppCheckToken suitable for consumption in limited-use scenarios + /// and then calls the provided callback function with the token or with an + /// error code and error message. + virtual void GetLimitedUseToken( + std::function + completion_callback) override; + private: FIRDeviceCheckProvider* ios_provider_; }; @@ -57,6 +64,20 @@ virtual void GetToken( }]; } +void DeviceCheckProvider::GetLimitedUseToken( + std::function + completion_callback) { + [ios_provider_ + getLimitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, + NSError* _Nullable error) { + completion_callback( + firebase::app_check::internal::AppCheckTokenFromFIRAppCheckToken( + token), + firebase::app_check::internal::AppCheckErrorFromNSError(error), + util::NSStringToString(error.localizedDescription).c_str()); + }]; +} + DeviceCheckProviderFactoryInternal::DeviceCheckProviderFactoryInternal() : created_providers_() { ios_provider_factory_ = std::make_unique( [[FIRDeviceCheckProviderFactory alloc] init]); From 2e011bc2160aee30a76d3940e5d1c2f40518ec8d Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Thu, 22 Jan 2026 11:03:44 -0500 Subject: [PATCH 06/10] remove the appcheckprovider limited from android The limited use token is not provided in the appcheck provider class --- app_check/src/android/app_check_android.cc | 77 ++++++------------- app_check/src/android/common_android.cc | 24 ++---- app_check/src/android/common_android.h | 2 - .../internal/cpp/JniAppCheckProvider.java | 15 ---- 4 files changed, 27 insertions(+), 91 deletions(-) diff --git a/app_check/src/android/app_check_android.cc b/app_check/src/android/app_check_android.cc index 567de8869c..12d8726d16 100644 --- a/app_check/src/android/app_check_android.cc +++ b/app_check/src/android/app_check_android.cc @@ -48,7 +48,8 @@ namespace internal { X(GetToken, "getAppCheckToken", \ "(Z)Lcom/google/android/gms/tasks/Task;"), \ X(GetLimitedUseToken, "getLimitedUseAppCheckToken", \ - "()Lcom/google/android/gms/tasks/Task;"), \ + "()Lcom/google/android/gms/tasks/Task;", \ + util::kMethodTypeInstance, util::kMethodOptional), \ X(AddAppCheckListener, "addAppCheckListener", \ "(Lcom/google/firebase/appcheck/FirebaseAppCheck$AppCheckListener;)V"), \ X(RemoveAppCheckListener, "removeAppCheckListener", \ @@ -111,17 +112,10 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken( JNIEnv* env, jobject j_provider, jlong c_provider, jobject task_completion_source); -JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetLimitedUseToken( - JNIEnv* env, jobject j_provider, jlong c_provider, - jobject task_completion_source); - static const JNINativeMethod kNativeJniAppCheckProviderMethods[] = { {"nativeGetToken", "(JLcom/google/android/gms/tasks/TaskCompletionSource;)V", - reinterpret_cast(JniAppCheckProvider_nativeGetToken)}, - {"nativeGetLimitedUseToken", - "(JLcom/google/android/gms/tasks/TaskCompletionSource;)V", - reinterpret_cast(JniAppCheckProvider_nativeGetLimitedUseToken)}}; + reinterpret_cast(JniAppCheckProvider_nativeGetToken)}}; // clang-format off #define JNI_APP_CHECK_LISTENER_METHODS(X) \ @@ -269,39 +263,6 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken( provider->GetToken(token_callback); } -JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetLimitedUseToken( - JNIEnv* env, jobject j_provider, jlong c_provider, - jobject task_completion_source) { - // Create GlobalReferences to the provider and task. These references will be - // deleted in the completion callback. - jobject j_provider_global = env->NewGlobalRef(j_provider); - jobject task_completion_source_global = - env->NewGlobalRef(task_completion_source); - - // Defines a C++ callback method to call - // JniAppCheckProvider.HandleGetTokenResult with the resulting token - auto token_callback{[j_provider_global, task_completion_source_global]( - firebase::app_check::AppCheckToken token, - int error_code, const std::string& error_message) { - // util::GetJNIEnvFromApp returns a threadsafe instance of JNIEnv. - JNIEnv* env = firebase::util::GetJNIEnvFromApp(); - jstring error_string = env->NewStringUTF(error_message.c_str()); - jstring token_string = env->NewStringUTF(token.token.c_str()); - env->CallVoidMethod( - j_provider_global, - jni_provider::GetMethodId(jni_provider::kHandleGetTokenResult), - task_completion_source_global, token_string, token.expire_time_millis, - error_code, error_string); - FIREBASE_ASSERT(!util::CheckAndClearJniExceptions(env)); - env->DeleteLocalRef(token_string); - env->DeleteLocalRef(error_string); - env->DeleteGlobalRef(j_provider_global); - env->DeleteGlobalRef(task_completion_source_global); - }}; - AppCheckProvider* provider = reinterpret_cast(c_provider); - provider->GetLimitedUseToken(token_callback); -} - JNIEXPORT void JNICALL JniAppCheckListener_nativeOnAppCheckTokenChanged( JNIEnv* env, jobject clazz, jlong c_app_check, jobject token) { auto app_check_internal = reinterpret_cast(c_app_check); @@ -492,21 +453,27 @@ Future AppCheckInternal::GetLimitedUseAppCheckToken() { JNIEnv* env = app_->GetJNIEnv(); auto handle = future()->SafeAlloc(kAppCheckFnGetLimitedUseAppCheckToken); - jobject j_task = env->CallObjectMethod( - app_check_impl_, app_check::GetMethodId(app_check::kGetLimitedUseToken)); - - std::string error = util::GetAndClearExceptionMessage(env); - if (error.empty()) { - auto data_handle = new FutureDataHandle(future(), handle); - util::RegisterCallbackOnTask(env, j_task, TokenResultCallback, - reinterpret_cast(data_handle), - jni_task_id_.c_str()); + jmethodID method_id = app_check::GetMethodId(app_check::kGetLimitedUseToken); + if (method_id != nullptr) { + jobject j_task = env->CallObjectMethod(app_check_impl_, method_id); + + std::string error = util::GetAndClearExceptionMessage(env); + if (error.empty()) { + auto data_handle = new FutureDataHandle(future(), handle); + util::RegisterCallbackOnTask(env, j_task, TokenResultCallback, + reinterpret_cast(data_handle), + jni_task_id_.c_str()); + } else { + AppCheckToken empty_token; + future()->CompleteWithResult(handle, kAppCheckErrorUnknown, error.c_str(), + empty_token); + } + env->DeleteLocalRef(j_task); } else { - AppCheckToken empty_token; - future()->CompleteWithResult(handle, kAppCheckErrorUnknown, error.c_str(), - empty_token); + // Fall back to the standard getAppCheckToken() if + // getLimitedUseAppCheckToken() is missing. + return GetAppCheckToken(false); } - env->DeleteLocalRef(j_task); return MakeFuture(future(), handle); } diff --git a/app_check/src/android/common_android.cc b/app_check/src/android/common_android.cc index 1ea48b6d0b..095c1f3c03 100644 --- a/app_check/src/android/common_android.cc +++ b/app_check/src/android/common_android.cc @@ -164,25 +164,11 @@ void AndroidAppCheckProvider::GetToken( void AndroidAppCheckProvider::GetLimitedUseToken( std::function completion_callback) { - JNIEnv* env = GetJniEnv(); - - jobject j_task = env->CallObjectMethod( - android_provider_, app_check_provider::GetMethodId( - app_check_provider::kGetLimitedUseToken)); - std::string error = util::GetAndClearExceptionMessage(env); - if (error.empty()) { - // Create an object to wrap the callback function - TokenResultCallbackData* completion_callback_data = - new TokenResultCallbackData(completion_callback); - util::RegisterCallbackOnTask( - env, j_task, TokenResultCallback, - reinterpret_cast(completion_callback_data), - jni_task_id_.c_str()); - } else { - AppCheckToken empty_token; - completion_callback(empty_token, kAppCheckErrorUnknown, error.c_str()); - } - env->DeleteLocalRef(j_task); + LogWarning( + "GetLimitedUseToken() was called, but the AppCheckProvider interface on " + "Android does not yet support limited-use tokens. Falling back to " + "GetToken()."); + GetToken(completion_callback); } } // namespace internal diff --git a/app_check/src/android/common_android.h b/app_check/src/android/common_android.h index 1daf9fdeb0..c0cbc81350 100644 --- a/app_check/src/android/common_android.h +++ b/app_check/src/android/common_android.h @@ -33,8 +33,6 @@ namespace internal { // clang-format off #define APP_CHECK_PROVIDER_METHODS(X) \ X(GetToken, "getToken", \ - "()Lcom/google/android/gms/tasks/Task;"), \ - X(GetLimitedUseToken, "getLimitedUseToken", \ "()Lcom/google/android/gms/tasks/Task;") // clang-format on diff --git a/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java b/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java index 43acd2e08a..365c61c823 100644 --- a/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java +++ b/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java @@ -41,15 +41,6 @@ public Task getToken() { return taskCompletionSource.getTask(); } - public Task getLimitedUseToken() { - TaskCompletionSource taskCompletionSource = - new TaskCompletionSource(); - // Call the C++ provider to get an AppCheckToken and set the task result. - // The C++ code will call handleGetTokenResult with the resulting token. - nativeGetLimitedUseToken(cProvider, taskCompletionSource); - return taskCompletionSource.getTask(); - } - /** * Called by C++ with a token in order to complete the java task. */ @@ -67,10 +58,4 @@ public void handleGetTokenResult(TaskCompletionSource taskComplet */ private native void nativeGetToken( long cProvider, TaskCompletionSource task_completion_source); - - /** - * This function is implemented in the AppCheck C++ library (app_check_android.cc). - */ - private native void nativeGetLimitedUseToken( - long cProvider, TaskCompletionSource task_completion_source); } From aea563344dec751ee49889538ff7407f674c96e1 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Thu, 22 Jan 2026 15:00:06 -0500 Subject: [PATCH 07/10] format fix --- .../integration_test/src/integration_test.cc | 3 +- app_check/src/desktop/app_check_desktop.cc | 26 +++++------ app_check/src/include/firebase/app_check.h | 9 ++-- app_check/src/ios/app_attest_provider_ios.mm | 21 ++++----- app_check/src/ios/app_check_ios.mm | 45 +++++++++---------- app_check/src/ios/debug_provider_ios.mm | 21 ++++----- .../src/ios/device_check_provider_ios.mm | 21 ++++----- 7 files changed, 65 insertions(+), 81 deletions(-) diff --git a/app_check/integration_test/src/integration_test.cc b/app_check/integration_test/src/integration_test.cc index 5b14374917..5a2d659c0c 100644 --- a/app_check/integration_test/src/integration_test.cc +++ b/app_check/integration_test/src/integration_test.cc @@ -532,7 +532,8 @@ TEST_F(FirebaseAppCheckTest, TestGetLimitedUseAppCheckToken) { firebase::Future<::firebase::app_check::AppCheckToken> future2 = app_check->GetLimitedUseAppCheckTokenLastResult(); - EXPECT_TRUE(WaitForCompletion(future2, "GetLimitedUseAppCheckTokenLastResult")); + EXPECT_TRUE( + WaitForCompletion(future2, "GetLimitedUseAppCheckTokenLastResult")); EXPECT_EQ(future.result()->expire_time_millis, future2.result()->expire_time_millis); } diff --git a/app_check/src/desktop/app_check_desktop.cc b/app_check/src/desktop/app_check_desktop.cc index f521a8131b..dce3b3a426 100644 --- a/app_check/src/desktop/app_check_desktop.cc +++ b/app_check/src/desktop/app_check_desktop.cc @@ -126,21 +126,21 @@ Future AppCheckInternal::GetLimitedUseAppCheckToken() { // Get a new token, and pass the result into the future. AppCheckProvider* provider = GetProvider(); if (provider != nullptr) { - auto token_callback{ - [this, handle](firebase::app_check::AppCheckToken token, int error_code, - const std::string& error_message) { - if (error_code == firebase::app_check::kAppCheckErrorNone) { - // Note that we do NOT update the cached token for limited-use tokens. - future()->CompleteWithResult(handle, 0, token); - } else { - future()->Complete(handle, error_code, error_message.c_str()); - } - }}; + auto token_callback{[this, handle](firebase::app_check::AppCheckToken token, + int error_code, + const std::string& error_message) { + if (error_code == firebase::app_check::kAppCheckErrorNone) { + // Note that we do NOT update the cached token for limited-use tokens. + future()->CompleteWithResult(handle, 0, token); + } else { + future()->Complete(handle, error_code, error_message.c_str()); + } + }}; provider->GetLimitedUseToken(token_callback); } else { - future()->Complete( - handle, firebase::app_check::kAppCheckErrorInvalidConfiguration, - "No AppCheckProvider installed."); + future()->Complete(handle, + firebase::app_check::kAppCheckErrorInvalidConfiguration, + "No AppCheckProvider installed."); } return MakeFuture(future(), handle); } diff --git a/app_check/src/include/firebase/app_check.h b/app_check/src/include/firebase/app_check.h index 1f74288630..dfd28b126f 100644 --- a/app_check/src/include/firebase/app_check.h +++ b/app_check/src/include/firebase/app_check.h @@ -153,12 +153,13 @@ class AppCheck { /// Returns the result of the most recent call to GetAppCheckToken(); Future GetAppCheckTokenLastResult(); - /// Requests a limited-use Firebase App Check token. This method should be used - /// ONLY if you need to authorize requests to a non-Firebase backend. Requests - /// to Firebase backends are authorized automatically if configured. + /// Requests a limited-use Firebase App Check token. This method should be + /// used ONLY if you need to authorize requests to a non-Firebase backend. + /// Requests to Firebase backends are authorized automatically if configured. Future GetLimitedUseAppCheckToken(); - /// Returns the result of the most recent call to GetLimitedUseAppCheckToken(); + /// Returns the result of the most recent call to + /// GetLimitedUseAppCheckToken(); Future GetLimitedUseAppCheckTokenLastResult(); /// Registers an {@link AppCheckListener} to changes in the token state. This diff --git a/app_check/src/ios/app_attest_provider_ios.mm b/app_check/src/ios/app_attest_provider_ios.mm index b2a991c68d..222fb01777 100644 --- a/app_check/src/ios/app_attest_provider_ios.mm +++ b/app_check/src/ios/app_attest_provider_ios.mm @@ -41,8 +41,7 @@ virtual void GetToken( /// and then calls the provided callback function with the token or with an /// error code and error message. virtual void GetLimitedUseToken( - std::function - completion_callback) override; + std::function completion_callback) override; private: FIRAppAttestProvider* ios_provider_; @@ -63,17 +62,13 @@ virtual void GetLimitedUseToken( } void AppAttestProvider::GetLimitedUseToken( - std::function - completion_callback) { - [ios_provider_ - getLimitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, - NSError* _Nullable error) { - completion_callback( - firebase::app_check::internal::AppCheckTokenFromFIRAppCheckToken( - token), - firebase::app_check::internal::AppCheckErrorFromNSError(error), - util::NSStringToString(error.localizedDescription).c_str()); - }]; + std::function completion_callback) { + [ios_provider_ getLimitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, + NSError* _Nullable error) { + completion_callback(firebase::app_check::internal::AppCheckTokenFromFIRAppCheckToken(token), + firebase::app_check::internal::AppCheckErrorFromNSError(error), + util::NSStringToString(error.localizedDescription).c_str()); + }]; } AppAttestProviderFactoryInternal::AppAttestProviderFactoryInternal() : created_providers_() {} diff --git a/app_check/src/ios/app_check_ios.mm b/app_check/src/ios/app_check_ios.mm index ef2bc8e48e..4778cbf78f 100644 --- a/app_check/src/ios/app_check_ios.mm +++ b/app_check/src/ios/app_check_ios.mm @@ -226,30 +226,27 @@ - (void)appCheckTokenDidChangeNotification:(NSNotification*)notification { __block SafeFutureHandle handle = future()->SafeAlloc(kAppCheckFnGetLimitedUseAppCheckToken); - [impl() limitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, - NSError* _Nullable error) { - AppCheckToken cpp_token = AppCheckTokenFromFIRAppCheckToken(token); - if (error != nil) { - NSLog(@"Unable to retrieve limited-use App Check token: %@", error); - int error_code = - firebase::app_check::internal::AppCheckErrorFromNSError(error); - std::string error_message = - util::NSStringToString(error.localizedDescription); - - future()->CompleteWithResult(handle, error_code, error_message.c_str(), - cpp_token); - return; - } - if (token == nil) { - NSLog(@"App Check token is nil."); - future()->CompleteWithResult(handle, kAppCheckErrorUnknown, - "AppCheck GetLimitedUseToken returned an " - "empty token.", - cpp_token); - return; - } - future()->CompleteWithResult(handle, kAppCheckErrorNone, cpp_token); - }]; + [impl() + limitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, NSError* _Nullable error) { + AppCheckToken cpp_token = AppCheckTokenFromFIRAppCheckToken(token); + if (error != nil) { + NSLog(@"Unable to retrieve limited-use App Check token: %@", error); + int error_code = firebase::app_check::internal::AppCheckErrorFromNSError(error); + std::string error_message = util::NSStringToString(error.localizedDescription); + + future()->CompleteWithResult(handle, error_code, error_message.c_str(), cpp_token); + return; + } + if (token == nil) { + NSLog(@"App Check token is nil."); + future()->CompleteWithResult(handle, kAppCheckErrorUnknown, + "AppCheck GetLimitedUseToken returned an " + "empty token.", + cpp_token); + return; + } + future()->CompleteWithResult(handle, kAppCheckErrorNone, cpp_token); + }]; return MakeFuture(future(), handle); } diff --git a/app_check/src/ios/debug_provider_ios.mm b/app_check/src/ios/debug_provider_ios.mm index 8f2538c733..fda3426da5 100644 --- a/app_check/src/ios/debug_provider_ios.mm +++ b/app_check/src/ios/debug_provider_ios.mm @@ -46,8 +46,7 @@ virtual void GetToken( /// and then calls the provided callback function with the token or with an /// error code and error message. virtual void GetLimitedUseToken( - std::function - completion_callback) override; + std::function completion_callback) override; private: FIRAppCheckDebugProvider* ios_provider_; @@ -69,17 +68,13 @@ virtual void GetLimitedUseToken( } void DebugAppCheckProvider::GetLimitedUseToken( - std::function - completion_callback) { - [ios_provider_ - getLimitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, - NSError* _Nullable error) { - completion_callback( - firebase::app_check::internal::AppCheckTokenFromFIRAppCheckToken( - token), - firebase::app_check::internal::AppCheckErrorFromNSError(error), - util::NSStringToString(error.localizedDescription).c_str()); - }]; + std::function completion_callback) { + [ios_provider_ getLimitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, + NSError* _Nullable error) { + completion_callback(firebase::app_check::internal::AppCheckTokenFromFIRAppCheckToken(token), + firebase::app_check::internal::AppCheckErrorFromNSError(error), + util::NSStringToString(error.localizedDescription).c_str()); + }]; } DebugAppCheckProviderFactoryInternal::DebugAppCheckProviderFactoryInternal() diff --git a/app_check/src/ios/device_check_provider_ios.mm b/app_check/src/ios/device_check_provider_ios.mm index 65a86c34eb..3f660fb639 100644 --- a/app_check/src/ios/device_check_provider_ios.mm +++ b/app_check/src/ios/device_check_provider_ios.mm @@ -42,8 +42,7 @@ virtual void GetToken( /// and then calls the provided callback function with the token or with an /// error code and error message. virtual void GetLimitedUseToken( - std::function - completion_callback) override; + std::function completion_callback) override; private: FIRDeviceCheckProvider* ios_provider_; @@ -65,17 +64,13 @@ virtual void GetLimitedUseToken( } void DeviceCheckProvider::GetLimitedUseToken( - std::function - completion_callback) { - [ios_provider_ - getLimitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, - NSError* _Nullable error) { - completion_callback( - firebase::app_check::internal::AppCheckTokenFromFIRAppCheckToken( - token), - firebase::app_check::internal::AppCheckErrorFromNSError(error), - util::NSStringToString(error.localizedDescription).c_str()); - }]; + std::function completion_callback) { + [ios_provider_ getLimitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token, + NSError* _Nullable error) { + completion_callback(firebase::app_check::internal::AppCheckTokenFromFIRAppCheckToken(token), + firebase::app_check::internal::AppCheckErrorFromNSError(error), + util::NSStringToString(error.localizedDescription).c_str()); + }]; } DeviceCheckProviderFactoryInternal::DeviceCheckProviderFactoryInternal() : created_providers_() { From 62a7b294e72e4609399ecd8fd3a01ea68c3abc57 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Thu, 22 Jan 2026 15:50:05 -0500 Subject: [PATCH 08/10] Clean up for review and simplify app check loader --- app_check/src/android/app_check_android.cc | 40 ++++++++++------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/app_check/src/android/app_check_android.cc b/app_check/src/android/app_check_android.cc index 12d8726d16..843d13d4d3 100644 --- a/app_check/src/android/app_check_android.cc +++ b/app_check/src/android/app_check_android.cc @@ -48,8 +48,7 @@ namespace internal { X(GetToken, "getAppCheckToken", \ "(Z)Lcom/google/android/gms/tasks/Task;"), \ X(GetLimitedUseToken, "getLimitedUseAppCheckToken", \ - "()Lcom/google/android/gms/tasks/Task;", \ - util::kMethodTypeInstance, util::kMethodOptional), \ + "()Lcom/google/android/gms/tasks/Task;"), \ X(AddAppCheckListener, "addAppCheckListener", \ "(Lcom/google/firebase/appcheck/FirebaseAppCheck$AppCheckListener;)V"), \ X(RemoveAppCheckListener, "removeAppCheckListener", \ @@ -238,13 +237,18 @@ JNIEXPORT jlong JNICALL JniAppCheckProviderFactory_nativeCreateProvider( JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken( JNIEnv* env, jobject j_provider, jlong c_provider, jobject task_completion_source) { + // Create GlobalReferences to the provider and task. These references will be + // deleted in the completion callback. jobject j_provider_global = env->NewGlobalRef(j_provider); jobject task_completion_source_global = env->NewGlobalRef(task_completion_source); + // Defines a C++ callback method to call + // JniAppCheckProvider.HandleGetTokenResult with the resulting token auto token_callback{[j_provider_global, task_completion_source_global]( firebase::app_check::AppCheckToken token, int error_code, const std::string& error_message) { + // util::GetJNIEnvFromApp returns a threadsafe instance of JNIEnv. JNIEnv* env = firebase::util::GetJNIEnvFromApp(); jstring error_string = env->NewStringUTF(error_message.c_str()); jstring token_string = env->NewStringUTF(token.token.c_str()); @@ -453,27 +457,21 @@ Future AppCheckInternal::GetLimitedUseAppCheckToken() { JNIEnv* env = app_->GetJNIEnv(); auto handle = future()->SafeAlloc(kAppCheckFnGetLimitedUseAppCheckToken); - jmethodID method_id = app_check::GetMethodId(app_check::kGetLimitedUseToken); - if (method_id != nullptr) { - jobject j_task = env->CallObjectMethod(app_check_impl_, method_id); - - std::string error = util::GetAndClearExceptionMessage(env); - if (error.empty()) { - auto data_handle = new FutureDataHandle(future(), handle); - util::RegisterCallbackOnTask(env, j_task, TokenResultCallback, - reinterpret_cast(data_handle), - jni_task_id_.c_str()); - } else { - AppCheckToken empty_token; - future()->CompleteWithResult(handle, kAppCheckErrorUnknown, error.c_str(), - empty_token); - } - env->DeleteLocalRef(j_task); + jobject j_task = env->CallObjectMethod( + app_check_impl_, app_check::GetMethodId(app_check::kGetLimitedUseToken)); + + std::string error = util::GetAndClearExceptionMessage(env); + if (error.empty()) { + auto data_handle = new FutureDataHandle(future(), handle); + util::RegisterCallbackOnTask(env, j_task, TokenResultCallback, + reinterpret_cast(data_handle), + jni_task_id_.c_str()); } else { - // Fall back to the standard getAppCheckToken() if - // getLimitedUseAppCheckToken() is missing. - return GetAppCheckToken(false); + AppCheckToken empty_token; + future()->CompleteWithResult(handle, kAppCheckErrorUnknown, error.c_str(), + empty_token); } + env->DeleteLocalRef(j_task); return MakeFuture(future(), handle); } From 677fa0611f70bb68da07657fa32cb841d6bf4aa9 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Thu, 22 Jan 2026 16:54:12 -0500 Subject: [PATCH 09/10] Fix comment to reflect what code is doing. --- app_check/src/android/common_android.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app_check/src/android/common_android.h b/app_check/src/android/common_android.h index c0cbc81350..89984772e7 100644 --- a/app_check/src/android/common_android.h +++ b/app_check/src/android/common_android.h @@ -59,9 +59,9 @@ class AndroidAppCheckProvider : public AppCheckProvider { void GetToken(std::function completion_callback) override; - /// Fetches an AppCheckToken suitable for consumption in limited-use scenarios - /// and then calls the provided callback function with the token or with an - /// error code and error message. + /// Fetches an AppCheckToken via a fallback method to the GetToken. The + /// current Android implementation does not support limited use tokens. For + /// custom App Check providers. void GetLimitedUseToken( std::function completion_callback) override; From 631dcde64de319bb06e1fd67d858565ecb2ae242 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Thu, 22 Jan 2026 17:21:11 -0500 Subject: [PATCH 10/10] Update app_check/src/common/app_check.cc Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- app_check/src/common/app_check.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app_check/src/common/app_check.cc b/app_check/src/common/app_check.cc index 5a74207950..4e3c065488 100644 --- a/app_check/src/common/app_check.cc +++ b/app_check/src/common/app_check.cc @@ -68,7 +68,7 @@ static std::map<::firebase::App*, AppCheck*>* g_app_check_map = nullptr; AppCheckListener::~AppCheckListener() {} AppCheckProvider::~AppCheckProvider() {} -// Default implimenation. Can be overriden by App check providers that support +// Default implementation. Can be overridden by App check providers that support // limited use tokens void AppCheckProvider::GetLimitedUseToken( std::function