Repository navigation
Conversation
- for auth with and without code exchange - only when receiving activity result with no pending operation, i.e. when no Result is available to send auth result to flutter - preserves null intent error because then no data is available to store
takes pending authorization data and returns it to new Result
afaik there is no scenario this can happen on iOS or macOS
MaikuB
left a comment
There was a problem hiding this comment.
Thanks for the PR. Can you update the example app so it shows how this could be used? This includes adding code comments to the example app where appropriate
There was a problem hiding this comment.
🟡 Changes recommended
Two critical Android issues and one moderate test issue remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds resumePendingAuthorization() to recover Android authorization results after Activity recreation, with typed Dart responses and iOS/macOS no-op support.
Changes:
- Adds the public resume API and response models.
- Implements Android pending-authorization handling.
- Adds platform mappings and method-channel tests.
File summaries
| File | Summary |
|---|---|
flutter_appauth/macos/flutter_appauth/Sources/flutter_appauth/FlutterAppauthPlugin.m |
Adds macOS no-op handling. |
flutter_appauth/lib/src/flutter_appauth.dart |
Exposes the resume API. |
flutter_appauth/lib/flutter_appauth.dart |
Exports resume response types. |
flutter_appauth/ios/flutter_appauth/Sources/flutter_appauth/FlutterAppauthPlugin.m |
Adds iOS no-op handling. |
flutter_appauth/ios/flutter_appauth/Sources/flutter_appauth/FlutterAppAuth.h |
Defines the resume method constant. |
flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/FlutterAppauthPlugin.java |
Implements pending authorization recovery. Critical (2 votes): preserve the exchange-flow flag before clearing state to report the correct failure code (lines 208 and 735). Critical (1 vote): persist pending authorization and exchange state across Activity/engine recreation (lines 92 and 738). |
flutter_appauth_platform_interface/test/method_channel_flutter_appauth_test.dart |
Tests response mapping. Moderate (2 votes): correct the epoch/timezone expectation at line 255. |
flutter_appauth_platform_interface/lib/src/method_channel_mappers.dart |
Adds response mapping helpers. |
flutter_appauth_platform_interface/lib/src/method_channel_flutter_appauth.dart |
Invokes the channel and maps responses. Nit (1 vote): preserve the original stack trace when rethrowing platform exceptions. |
flutter_appauth_platform_interface/lib/src/flutter_appauth_platform.dart |
Adds the platform interface method. |
flutter_appauth_platform_interface/lib/src/authorization_resume_response.dart |
Defines typed resume responses. |
flutter_appauth_platform_interface/lib/flutter_appauth_platform_interface.dart |
Exports the new response model. |
Review details
Suppressed comments (3)
flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/FlutterAppauthPlugin.java:739
- On a recreated plugin, the request-scoped
clientSecretandallowInsecureConnectionsfields have reverted to their defaults because they are only populated by the original method call. This reusesprocessAuthorizationData, which reads those fields during the code exchange, so resumedauthorizeAndExchangeCodeflows for confidential clients or non-HTTPS endpoints fail. Persist these options inPendingAuthorizationand use them for the resumed exchange (or require them as resume input).
checkAndSetPendingOperation(RESUME_PENDING_AUTHORIZATION_METHOD, result);
processAuthorizationData(pendingAuth.response, pendingAuth.exception, pendingAuth.exchangeCode);
flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/FlutterAppauthPlugin.java:739
- If the app starts another operation after a response has been stashed but before it calls this method,
pendingOperationis non-null here. The code clears the stashed response beforecheckAndSetPendingOperationthrows; the outer catch then completes the unrelated operation viafinishWithError, while theresumePendingAuthorizationResult is never completed. Check for an in-flight operation and fail the resume call without consuming the pending response.
final PendingAuthorization pendingAuth = pendingAuthorization;
pendingAuthorization = null;
checkAndSetPendingOperation(RESUME_PENDING_AUTHORIZATION_METHOD, result);
processAuthorizationData(pendingAuth.response, pendingAuth.exception, pendingAuth.exchangeCode);
flutter_appauth_platform_interface/lib/src/method_channel_flutter_appauth.dart:113
- The previous implementation used
rethrowfor platform exceptions without usable details. Moving this branch into a helper and usingthrow eresets the Dart stack trace to the helper (the identical branch below has the same problem), changing the existing pass-through behavior and making caller failures harder to diagnose. Preserve the original stack trace when delegating this exception, or keep the pass-through rethrow in the caller.
Never _mapAndThrowPlatformException(PlatformException e) {
if (e.details == null) {
throw e;
}
- Files reviewed: 12/12 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Hey @MaikuB, thanks for the review. I have added this feature to the example app and fixed 2/3 Copilot findings (the 3rd being not really useful). |
| Activity will have been destroyed and recreated in the background. | ||
| Always returns null on other platforms, so can be called unconditionally. | ||
| */ | ||
| _resumePendingAuthorization(); |
There was a problem hiding this comment.
Let me know if I have missed something but since this is about resuming, shouldn't this hook into didChangeAppLifecycleState and hooked into the AppLifecycleState.resumed state?
There was a problem hiding this comment.
The "resume" part only really refers to the auth flow itself, not necessarily the app. The flutter app is restarted entirely when the host Activity is recreated. That means all widgets are reinitialized as well. Calling _resumePendingAuthorization() in the app's lifecycle resume would probably not cause any issues, but wouldn't really do anything either. If the app is actually resuming, it's probably the "normal" case that doesn't need this fix.
To summarise, I wouldn't call this in AppLifecycleState.resumed. We have been using this fix for a while now just like this (in initState on the login page) and it seems to have fixed the problems, both during testing and in production.
There was a problem hiding this comment.
Thanks I knew the resume part was more to do with auth flow. However, low memory scenarios are supposed to retain the app/activity and then trigger resume lifecycle events. This was the reason behind my comment.
Either way, I'm having trouble trying to ensure the solution works with the example app as the "don't keep activities" kills the Chrome Custom Tab and counts as cancelling the auth attempt. Let me know if you know of a different way to test
There was a problem hiding this comment.
@MaikuB I have tested around a bit more. The setup in the example app is of course quite a bit simpler than our app. It seems that, when "Don't keep activities" is active, there is no reliable order between initState() and the app's resume. I noticed a difference if I started the app with the debugger attached vs. just launching it from the emulator at the very least.
That being said, I now agree with also calling resumePendingAuthorization() on resumed events. I'd also keep the call in initState though, since the order seems unreliable and the cost of calling it redundantly is relatively low. I've pushed this change already.
As for the testing, I don't quite understand what you mean by the Chrome tab being killed and the auth flow cancelled. I had no issue testing this on real devices and emulator. I have recorded three test scenarios, maybe that helps:
Commented out _resumePendingAuthorization() (in both initState and resumed state) -> resume is not triggered automatically. When returning from the browser, nothing happens initially. Only when manually pressing the "Resume pending authorization" button do the tokens appear.
manual_resume.webm
With the automatic resume (so just the PR branch without local changes) -> tokens appear after short delay.
auto_resume.webm
Current main branch -> nothing happens after returning from the browser.
main_branch.webm
Let me know if I can help any further.
resume should probably have lower priority than "regular" auth calls. this ensures that resume results in an error, but the actual authentication is not affected when resume is called during it. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>


We have encountered an issue where the auth response is not handled after returning from the browser back to the app. This happens when the app's Activity is killed by Android (e.g. for clearing up memory). This PR adds a resumePendingAuthorization() method that allows the app to get the "missed" auth response after reinitialising itself.
Steps to reproduce:
=> Browser closes, but app doesn't receive the auth response.
Behaviour with fix: