AppCheck do not schedule an auto-refresh if a stored token exists
[REQUIRED] Step 2: Describe your environment
- Android Studio version: 2022.3.1
- Firebase Component: AppCheck
- Component version: 32.2.0
[REQUIRED] Step 3: Describe the problem
Steps to reproduce:
When we call getAppCheckToken with forceRefresh as false, if we have a valid token stored in the cachedToken there is no call to tokenRefreshManager to schedule the next auto-refresh (if enable). Also, when the library is initialised and DefaultFirebaseAppCheck is created we call retrieveStoredAppCheckTokenInBackground to read and cache the stored token, but no call to tokenRefreshManager is done.
This means that if we restart the app with a valid token, we loose the auto-refresh feature (if enabled) and the next update will be when the cached token expires, the library will not try to refresh it in background.
Relevant Code:
private Task<Void> retrieveStoredAppCheckTokenInBackground(@NonNull Executor executor) {
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();
executor.execute(
() -> {
AppCheckToken token = storageHelper.retrieveAppCheckToken();
if (token != null) {
setCachedToken(token);
}
taskCompletionSource.setResult(null);
});
return taskCompletionSource.getTask();
}
public Task<AppCheckToken> getAppCheckToken(boolean forceRefresh) {
return retrieveStoredTokenTask.continueWithTask(
liteExecutor,
unused -> {
if (!forceRefresh && hasValidToken()) {
return Tasks.forResult(cachedToken);
}
if (appCheckProvider == null) {
return Tasks.forException(new FirebaseException("No AppCheckProvider installed."));
}
return fetchTokenFromProvider();
});
}
One solution may be call tokenRefreshManager.maybeScheduleTokenRefresh(token); after setCachedToken(token); in retrieveStoredAppCheckTokenInBackground
I found a few problems with this issue:
- I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
- This issue does not seem to follow the issue template. Make sure you provide all the required information.
Hi @Gazer, thanks for reaching out. Reading through the codebase does seem to do as how you explained it to be.
Could you clarify a few things for me: When you mentioned that "we loose the auto-refresh feature (if enabled) and the next update will be when the cached token expires, the library will not try to refresh it in background.",
- How do we loose the auto-refresh feature when a cache token is present? I'm not seeing where this is happening in the codebase.
- Do you mean that if the cache token is expired and the app is closed. The app will not refresh the token in the background, but will wait until the app is reopened again before a new token is retrieved?
- We use ApCheck with a custom backend, not with Firebase services. When we start the app and we already have a valid token, the refreshManager is not creating the schedule task to refresh the token automatically. In practice, what happens is that we do not receive the call to the callback with the new token when the token is about to expire.
- No, we do not expect to get a refresh with the app in background, but if we start the app with a valid token that expire in X minutes, we expect to get the token auto-refresh 5 minutes before (as per the current implementation of Appcheck library) the token expires.
Thanks for the extra details, @Gazer. This clarifies a lot. That said, I'll try and simulate this scenario. If there's any chance you have a minimal reproducible example ready, please do share it with us. It'll really help us speed up the investigation. Thanks!
After further investigation, it seems to me that the SDK is already scheduling auto-refresh in the TokenRefreshManager during background state changes:
https://github.com/firebase/firebase-android-sdk/blob/dff55b6c555ab90877b473c4f22a91cd6f3e50a9/appcheck/firebase-appcheck/src/main/java/com/google/firebase/appcheck/internal/TokenRefreshManager.java#L76-L79
I've tested this with a custom provider and the scheduler seems to schedule the refresh correctly (approx. 30 minutes), though I haven't tested this fully of waiting for it to expire then refresh since this requires 30+ minutes just to test the scenario. In the meantime, could you point me where in our documentation that states auto-refresh is 5 minutes prior to expiration, we might need to update that? Usually the refresh should be immediate if the token is around half of the TTL.
The default TTL of 1 hour is reasonable for most apps. Note that the App Check library refreshes tokens at approximately half the TTL duration.
In your case, is your app not receiving the new tokens when the TTL is about to expire? If so, could you share code snippets or minimal repro so we can investigate this further since I'm unable to reproduce the issue?
No, we do not expect to get a refresh with the app in background, but if we start the app with a valid token that expire in X minutes, we expect to get the token auto-refresh 5 minutes before (as per the current implementation of Appcheck library) the token expires.
In this scenario, are these the correct steps to reproduce this behavior:
- Open app, TTL will expire in 1 hr
- Keep app open, do nothing for 50 minutes
- app does not refresh token, even after token expires(?)
👋 Jumping in (I'm a colleague of @Gazer)!
Did you try to restart the application? It does seem like the first time a token is obtained the RefreshManager is correctly running.
However once there is a valid one and the app would be started, there is no refresh intent. The token shown in the screenshot is expected to be refreshed into ~30 minutes TTL.
Here is a repo with minimal implementation, although there isn't anything surprising: https://github.com/neugartf/bug_5235.
Thanks for the help!
Hey @Gazer. We need more information to resolve this issue but there hasn't been an update in 5 weekdays. I'm marking the issue as stale and if there are no new updates in the next 5 days I will close it automatically.
If you have more information that will help us get to the bottom of this, just add a comment!
@argzdev you can check the example repo in the previous comment to reproduce our problem.
Thanks for the extra details and minimal repro, @neugartf and @Gazer. Give me a few days to work on this. I'll keep you posted once I compile my findings and I'll try to bring this up to our eng sync to get some feedback as well. Thanks!
Hi @Gazer and @neugartf, I was able to reproduce the same behavior and forwarded the findings to our engineers. I noticed that the token refresh intent does the refresh exceeding close to the expiry.
Scenario 1: Testing with DebugAppCheckProviderFactory. App displays the expiry on app start, and shows the current token on a button press. App is reopened multiple times
- Expected behavior: Token should refresh around half remaining time before expiry
- Test 1 actual behavior: Token refreshed nearing expiration at around 6 minutes before expiry
- Test 2 actual behavior: Token refreshed nearing expiration at around 4 minutes before expiry
Scenario 2: Testing with CustomAppCheckProviderFactory. App check token via cloud functions. The app displays the expiry on app start, and shows the current token on a button press. App Check TTL is set at 60 minutes, app is reopened multiple times
- Expected behavior: Token should refresh around half remaining time before expiry
- Test 1 actual behavior: Token refreshed nearing expiration at around 10 minutes before expiry
- Test 2 actual behavior: Token refreshed nearing expiration at around 5 minutes before expiry
- Test 3 actual behavior: Token refreshed nearing expiration at around 5 minutes before expiry
Scenario 3 (Correct behavior, but only happens when app is opened once): Testing with DebugAppCheckProviderFactory. App displays the expiry on app start, and shows the current token on a button press. App is opened once.
- Expected behavior: Token should refresh around half remaining time before expiry
- Actual behavior: Token refreshed 25 minutes before expiry (Correct)
Scenario 4: Testing with CustomAppCheckProviderFactory. App check token via cloud functions. The app displays the expiry on app start, and shows the current token on a button press. App is opened once.
- Expected behavior: Token should refresh around half remaining time before expiry
- Actual behavior: Token refreshed 24 minutes before expiry (Correct)
In your case, when you call getAppCheckToken and retrieveStoredAppCheckTokenInBackground, do you also exhibit the behavior where the token refreshes nearing the expiry or does the token refreshes when it is expired? In my understanding, if the token refreshes when it is expired, it's possible that there will be instances where some requests would be categorized as unverified. Are you also experiencing this behavior?
That said, our engineers will be investigating this issue and will reply back here once we get updates.
Hey @argzdev , thanks for the investigation! I think we have seen what you have described. The token refresh only happens nearing the expiry date time.
Got it, thanks for the quick update. Internal tracked at b/300121518
Any chance this is getting fixed soon?
@argzdev Are there any updates on this issue? It's been almost 2 years since the last comment, so I wanted to check whether you are marking this issue as non-fix.
@argzdev Are there any updates on this issue? It's been almost 2 years since the last comment, so I wanted to check whether you are marking this issue as non-fix.
Wondering this too! Im running into a similar issue it appears (FYI Im using Flutter for iOS development - with App Attest as my chosen attestation provider). Im assuming its an issue with my attestation token not regenerating automatically before the TTL expires. Currently Im looking into writing logic to re-trigger the generation of a new attestation token upon failure -- but its a bit perplexing as documentation mentions that a new attestation token should be automatically generated by the SDK at the halfway point in the TTL lifecycle. Any help would be appreciated!
Hey folks! Sorry for the silence here. I looked into the internal bug and there are currently no new developments. I asked for a follow up and we’ll let you know if there’s any update.
Hey folks! Sorry for the silence here. I looked into the internal bug and there are currently no new developments. I asked for a follow up and we’ll let you know if there’s any update.
Thanks very much! I've done some more research/testing on the issue and think I'm closer to understanding what could be going on (in my particular case at least).
I am using AppCheck to add additional security measures to my Firestore database. I've found some conflicting intel online about recommended AppCheck/Firestore DB rules. I believe the addition of "request.appCheck != null" was causing traffic to fail (which I may have mistakenly taken as a token TTL issue)-- I cannot find any actual documentation for request.AppCheck, or any existence of request.AppCheck within the Firestore Rule linter. Am I correct to assume that simply enabling Firestore AppCheck in the console is sufficient, and that there is no need to directly write logic for it within the Firestore rule?
For further context, my current rules for my Firestore are only checking for the existence of request.auth. AppCheck for firestore is also enabled within the console (with all current traffic being verified).
rules_version = '2';
service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.auth != null; } } }
Any insight would be appreciated! And hopefully this helps some others too.
Thanks!