Is there an existing issue for this?
Which plugins are affected?
App Check
Which platforms are affected?
iOS, macOS
Description
On iOS (and macOS), FirebaseAppCheck.instance.getLimitedUseToken() returns a standard App Check token with a 1-hour TTL instead of a limited-use token (5-minute TTL). Every Apple provider goes through the same wrapper (see root cause), so this applies to debug, App Attest, DeviceCheck and reCAPTCHA alike. I reproduced it with the debug provider.
As a result, anything that relies on limited-use tokens fails on Apple platforms once replay protection is enforced. For example, Firebase AI Logic with FirebaseAI.googleAI(useLimitedUseAppCheckTokens: true) rejects every request, and the App Check console reports them as "Unverified: Reused token". The same app on Android works, because Android returns proper 5-minute limited-use tokens.
Root cause. The plugin installs its own provider through FlutterAppCheckProviderFactory → AppCheckProviderWrapper (ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift). The wrapper implements only getToken(completion:). getLimitedUseToken(completion:) is an optional method of AppCheckProvider, added in firebase-ios-sdk #12067. So FIRInternalAppCheckProvider finds that the wrapper doesn't implement it and silently falls back to getToken:
- (void)getLimitedUseTokenWithCompletion:(...)handler {
if ([self.appCheckProvider respondsToSelector:@selector(getLimitedUseTokenWithCompletion:)]) {
...
} else {
[self getTokenWithCompletion:handler];
}
}
The wrapped provider (for example AppCheckDebugProvider, which does send limited_use: true when asked) is therefore never asked for a limited-use token.
History. The wrapper (#10401, Mar 2023) and getLimitedUseToken() (#11091, Jun 2023) both predate the optional protocol method, so neither needed it at the time. react-native-firebase, whose wrapper design the plugin followed, added the forwarding in invertase/react-native-firebase#7424 (Nov 2023). FlutterFire never made the matching change, and the Swift rewrite (#18569) kept the same behaviour. #12468 appears to have hit the same symptom (a 1-hour token from getLimitedUseToken()) but was closed without a diagnosis.
The getLimitedUseToken e2e test didn't catch this because it only checks that an exception is thrown when no debug token is configured; it never looks at the returned token.
Reproducing the issue
- Activate App Check with any Apple provider, e.g.
FirebaseAppCheck.instance.activate(providerApple: const AppleDebugProvider()), and register the debug token.
- Call
await FirebaseAppCheck.instance.getLimitedUseToken().
- Decode the JWT:
exp - iat is 3600. It should be 300.
End to end: use firebase_ai with useLimitedUseAppCheckTokens: true and replay protection enforced for Firebase AI Logic. Every iOS request is rejected ("Unverified: Reused token" in the console), while Android succeeds.
Verified on a physical iPhone (iOS 26.6.2): TTL 3600 s and rejected before the fix, TTL 300 s and HTTP 200 after forwarding the method. Android emulator: TTL 300 s and HTTP 200 unchanged.
Firebase Core version
4.15.0 (firebase_app_check 0.4.8; still present on main)
Flutter Version
3.47.5
Relevant Log Output
Request token claims (iOS, before fix): exp - iat = 3600s → HTTP 401 "Firebase App Check token is invalid."
Request token claims (iOS, after fix): exp - iat = 300s → HTTP 200
Request token claims (Android): exp - iat = 300s → HTTP 200
Flutter dependencies
firebase_core 4.15.0
firebase_app_check 0.4.8
firebase_ai 4.0.0
Firebase Apple SDK 12.19.0 (CocoaPods)
Additional context and comments
I have a fix ready (forward getLimitedUseToken(completion:) in AppCheckProviderWrapper, plus an e2e assertion on the TTL) and will open a PR referencing this issue.
Is there an existing issue for this?
Which plugins are affected?
App Check
Which platforms are affected?
iOS, macOS
Description
On iOS (and macOS),
FirebaseAppCheck.instance.getLimitedUseToken()returns a standard App Check token with a 1-hour TTL instead of a limited-use token (5-minute TTL). Every Apple provider goes through the same wrapper (see root cause), so this applies to debug, App Attest, DeviceCheck and reCAPTCHA alike. I reproduced it with the debug provider.As a result, anything that relies on limited-use tokens fails on Apple platforms once replay protection is enforced. For example, Firebase AI Logic with
FirebaseAI.googleAI(useLimitedUseAppCheckTokens: true)rejects every request, and the App Check console reports them as "Unverified: Reused token". The same app on Android works, because Android returns proper 5-minute limited-use tokens.Root cause. The plugin installs its own provider through
FlutterAppCheckProviderFactory→AppCheckProviderWrapper(ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift). The wrapper implements onlygetToken(completion:).getLimitedUseToken(completion:)is an optional method ofAppCheckProvider, added in firebase-ios-sdk #12067. SoFIRInternalAppCheckProviderfinds that the wrapper doesn't implement it and silently falls back togetToken:The wrapped provider (for example
AppCheckDebugProvider, which does sendlimited_use: truewhen asked) is therefore never asked for a limited-use token.History. The wrapper (#10401, Mar 2023) and
getLimitedUseToken()(#11091, Jun 2023) both predate the optional protocol method, so neither needed it at the time. react-native-firebase, whose wrapper design the plugin followed, added the forwarding in invertase/react-native-firebase#7424 (Nov 2023). FlutterFire never made the matching change, and the Swift rewrite (#18569) kept the same behaviour. #12468 appears to have hit the same symptom (a 1-hour token fromgetLimitedUseToken()) but was closed without a diagnosis.The
getLimitedUseTokene2e test didn't catch this because it only checks that an exception is thrown when no debug token is configured; it never looks at the returned token.Reproducing the issue
FirebaseAppCheck.instance.activate(providerApple: const AppleDebugProvider()), and register the debug token.await FirebaseAppCheck.instance.getLimitedUseToken().exp - iatis 3600. It should be 300.End to end: use
firebase_aiwithuseLimitedUseAppCheckTokens: trueand replay protection enforced for Firebase AI Logic. Every iOS request is rejected ("Unverified: Reused token" in the console), while Android succeeds.Verified on a physical iPhone (iOS 26.6.2): TTL 3600 s and rejected before the fix, TTL 300 s and HTTP 200 after forwarding the method. Android emulator: TTL 300 s and HTTP 200 unchanged.
Firebase Core version
4.15.0 (firebase_app_check 0.4.8; still present on
main)Flutter Version
3.47.5
Relevant Log Output
Flutter dependencies
Additional context and comments
I have a fix ready (forward
getLimitedUseToken(completion:)inAppCheckProviderWrapper, plus an e2e assertion on the TTL) and will open a PR referencing this issue.