Skip to content

AppAuth iOS Crash: OIDAuthorizationSession resumeExternalUserAgentFlowWithURL — cannot reproduce locally #634

@MSL-BIT

Description

@MSL-BIT

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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions