-
Notifications
You must be signed in to change notification settings - Fork 278
Description
We are seeing a large number of crashes in production originating inside AppAuth on iOS.
The crash consistently occurs in:
-[OIDAuthorizationSession resumeExternalUserAgentFlowWithURL:]AppAuth throws the internal OIDAuthExceptionInvalidAuthorizationFlow exception here:
if (!_pendingauthorizationFlowCallback) {
[NSException raise:OIDAuthExceptionInvalidAuthorizationFlow
format:@"%@", OIDAuthExceptionInvalidAuthorizationFlow];
}This indicates that AppAuth receives a redirect URL at a time when no active authorization session exists anymore.
The problem is that this exception is thrown at the native (Objective-C) level, which completely terminates the Flutter app. We cannot catch or prevent it from the Dart side.
We cannot reproduce this locally on any test device — the problem only occurs in production.
Here is the (anonymised) structure of our sign-in method.
We use a standard pattern:
Future<Either<Failure, AuthTokens>> signIn({
bool forceRefreshSession = false,
}) async {
try {
// 1. Check connectivity
// 2. Use cached token if still valid (unless forced refresh)
// 3. Try refresh token flow
// 4. If refresh fails → run full OAuth flow
final result = await _performFullAuthFlow();
return Right(result);
} on PlatformException {
return Left(AuthCanceledOrConnectionFailure());
} catch (_) {
return Left(AuthUnknownFailure());
}
}
await appAuth.authorizeAndExchangeCode(AuthorizationRequest(...));We also use a refresh flow with deduplication (preventing concurrent refresh calls):
Future<AuthTokens> _refreshToken() async {
var completer = _completer;
if (completer != null && !completer.isCompleted) {
// A refresh is already in progress → wait for it
return completer.future;
}
completer = Completer<AuthTokens>();
_completer = completer;
// iOS-specific delay (helps with timing issues)
if (DeviceHelper.isIOS) {
await Future.delayed(Duration(milliseconds: 900));
}
final result = await _authService.signIn(forceRefreshSession: true);
result.fold(
(failure) => completer.completeError(failure),
(success) => completer.complete(success),
);
return completer.future;
}🙏 Help Appreciated!
Any guidance — whether from maintainers, package authors, or other developers who have dealt with similar issues in production — would be incredibly valuable.
Thank you!