Skip to content

[webview_flutter_wkwebview] Crash: Could not cast value of type 'NSNull' to 'AuthenticationChallengeResponse' when the instance manager is cleared while an auth challenge is in flight #191254

Closed

Description

@bayraktarmdkaraca

Steps to Reproduce

  1. Create an app with a scene manifest (UIApplicationSceneManifest in Info.plist, default for recent Xcode templates).
  2. Show a WebViewWidget loading any https page, so WKNavigationDelegate.didReceiveAuthenticationChallenge round-trips to Dart for the server-trust challenge.
  3. While a page load is in progress, background the app so iOS disconnects the scene (or let the app terminate).

Expected results

No crash. A late or unresolvable authentication-challenge reply should fall back to performDefaultHandling (or surface a Pigeon error), as the null-return branch clearly intends.

Actual results

The app crashes with:

Could not cast value of type 'NSNull' (0x...) to 'webview_flutter_wkwebview.AuthenticationChallengeResponse' (0x...).

Analysis

WebViewFlutterPlugin.tearDownProxyAPIRegistrar() (called from sceneDidDisconnect and applicationWillTerminate) calls
instanceManager.removeAllObjects(). A didReceiveAuthenticationChallenge reply that is already in flight then carries an
identifier that no longer resolves:

WebKitLibrary.g.swift (3.26.0), codec reader:

case 128:
  let identifier = self.readValue()
  let instance: AnyObject? = pigeonRegistrar.instanceManager.instance(forIdentifier: ...)
  if instance == nil {
    print("Failed to find instance with identifier: \(identifier!)")
  }
  return instance

FlutterStandardReader substitutes NSNull for a nil element when building the reply list, so the existing guard in
didReceiveAuthenticationChallenge does not catch it:

} else if listResponse[0] == nil {        // NSNull is not nil -> guard passes
  ...
} else {
  let result = listResponse[0] as! AuthenticationChallengeResponse   // <-- crash
  completion(.success(result))
}

The same as! pattern after an == nil guard is used for every non-null Pigeon return value in the generated file, so this is
a general Pigeon codegen issue, not specific to the auth-challenge callback — but the auth challenge is the easiest one to hit
in production because it fires on ordinary https loads.

Suggested fix

Either treat NSNull as null in the generated guard (listResponse[0] == nil || listResponse[0] is NSNull), or use a safe
cast with an error completion instead of as!. Additionally, didReceiveAuthenticationChallenge could complete with
performDefaultHandling when the response cannot be resolved after teardown.

Versions

  • webview_flutter_wkwebview 3.26.0 (latest at time of report), webview_flutter 4.14.1
  • Flutter 3.44.9 stable, engine 5a2a6a4
  • iOS, physical devices, release build

Activity

  1. suryaKommana2662 commented on Aug 18, 2026

    @suryaKommana2662
    Contributor

    @bayraktarmdkaraca Thanks for the detailed report and analysis. Could you please provide a minimal reproducible example that demonstrates the crash? Additionally, could you please verify whether the issue is still reproducible on the latest stable Flutter version? and also provide fluter doctor -v output. This will help us confirm the issue and provide a reliable reproduction for further investigation.

  2. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    and removed
    in triagePresently being triaged by the triage team
    on Aug 18, 2026
  3. bayraktarmdkaraca commented on Aug 19, 2026

    @bayraktarmdkaraca
    Author

    Thanks for looking at this. Minimal reproduction, verification on the latest stable, and flutter doctor -v below.

    Reproduction

    The crash happens whenever Dart answers WKNavigationDelegate.didReceiveAuthenticationChallenge with an AuthenticationChallengeResponse whose identifier the native PigeonInstanceManager cannot resolve: the codec reader returns nil, FlutterStandardReader substitutes NSNull, the listResponse[0] == nil guard in WebKitLibrary.g.swift does not catch NSNull, and the following as! aborts the process.

    In production that unresolvable identifier is produced by the plugin itself — WebViewFlutterPlugin.tearDownProxyAPIRegistrar() calls instanceManager.removeAllObjects(), and iOS triggers it through sceneDidDisconnect / applicationWillTerminate while a challenge is still in flight. That is a race and hard to hit on demand, so the sample below puts the app in the same state deterministically through PigeonOverrides.authenticationChallengeResponse_createAsync. Everything else — the real WKWebView, a real httpBasic challenge and the plugin's own didReceiveAuthenticationChallenge implementation — is untouched.

    Setup:

    flutter create --platforms=ios wkwebview_auth_crash
    cd wkwebview_auth_crash
    flutter pub add webview_flutter webview_flutter_wkwebview
    # replace lib/main.dart with the file below
    # add NSAppTransportSecurity / NSAllowsLocalNetworking = true to ios/Runner/Info.plist
    #   (only needed for the local http://127.0.0.1 server that raises the challenge)
    flutter run

    The app aborts as soon as the local server asks for credentials — no interaction needed.

    lib/main.dart
    // Minimal reproduction of the force-cast crash in webview_flutter_wkwebview:
    //
    //   Could not cast value of type 'NSNull' (0x...) to
    //   'webview_flutter_wkwebview.AuthenticationChallengeResponse' (0x...).
    //
    // The crash happens when Dart answers `WKNavigationDelegate.
    // didReceiveAuthenticationChallenge` with an `AuthenticationChallengeResponse`
    // whose identifier the *native* `PigeonInstanceManager` cannot resolve.
    //
    // In production that state is produced by the plugin itself:
    // `WebViewFlutterPlugin.tearDownProxyAPIRegistrar()` calls
    // `instanceManager.removeAllObjects()`, and iOS triggers it through
    // `sceneDidDisconnect` / `applicationWillTerminate` while a challenge is still
    // in flight. That is a race and therefore awkward to reproduce on demand, so
    // this sample puts the app in the same state deterministically: the response
    // object exists in the Dart instance manager but not in the native one.
    //
    // Everything else is the plugin's own code path: a real WKWebView, a real
    // httpBasic challenge, and the plugin's own `didReceiveAuthenticationChallenge`
    // implementation.
    //
    // Run on an iOS device or simulator:
    //
    //   flutter run
    //
    // The app crashes with SIGABRT as soon as the local server asks for
    // credentials. Expected instead: a Pigeon error (or a fallback to
    // `performDefaultHandling`), not a hard crash.
    
    import 'dart:async';
    import 'dart:io';
    
    import 'package:flutter/material.dart';
    import 'package:webview_flutter/webview_flutter.dart';
    // ignore: implementation_imports
    import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart';
    
    void main() {
      // Hand the plugin a response that the native instance manager does not know
      // about - the exact state `removeAllObjects()` leaves the app in.
      PigeonOverrides.authenticationChallengeResponse_createAsync = (
        UrlSessionAuthChallengeDisposition disposition,
        URLCredential? credential,
      ) async {
        final response = AuthenticationChallengeResponse.pigeon_detached(
          disposition: disposition,
          credential: credential,
        );
        PigeonInstanceManager.instance.addDartCreatedInstance(response);
        return response;
      };
    
      runApp(const ReproApp());
    }
    
    class ReproApp extends StatelessWidget {
      const ReproApp({super.key});
    
      @override
      Widget build(BuildContext context) => const MaterialApp(home: ReproPage());
    }
    
    class ReproPage extends StatefulWidget {
      const ReproPage({super.key});
    
      @override
      State<ReproPage> createState() => _ReproPageState();
    }
    
    class _ReproPageState extends State<ReproPage> {
      WebViewController? _controller;
      String _status = 'Starting the local server...';
    
      @override
      void initState() {
        super.initState();
        _start();
      }
    
      Future<void> _start() async {
        final int port = await _startBasicAuthServer();
    
        final controller = WebViewController()
          ..setJavaScriptMode(JavaScriptMode.unrestricted)
          ..setNavigationDelegate(
            NavigationDelegate(
              onHttpAuthRequest: (HttpAuthRequest request) {
                setState(() => _status = 'Auth challenge received, answering it -> crash');
                request.onProceed(const WebViewCredential(user: 'user', password: 'password'));
              },
            ),
          );
    
        await controller.loadRequest(Uri.parse('http://127.0.0.1:$port/'));
    
        setState(() {
          _controller = controller;
          _status = 'Loading http://127.0.0.1:$port/';
        });
      }
    
      /// Always answers 401 so WKWebView raises an httpBasic challenge.
      Future<int> _startBasicAuthServer() async {
        final HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
        server.listen((HttpRequest request) async {
          request.response
            ..statusCode = HttpStatus.unauthorized
            ..headers.set(HttpHeaders.wwwAuthenticateHeader, 'Basic realm="repro"')
            ..write('unauthorized');
          await request.response.close();
        });
        return server.port;
      }
    
      @override
      Widget build(BuildContext context) {
        final WebViewController? controller = _controller;
        return Scaffold(
          appBar: AppBar(title: const Text('wkwebview auth challenge crash')),
          body: Column(
            children: <Widget>[
              Padding(padding: const EdgeInsets.all(16), child: Text(_status)),
              Expanded(
                child: controller == null
                    ? const Center(child: CircularProgressIndicator())
                    : WebViewWidget(controller: controller),
              ),
            ],
          ),
        );
      }
    }

    Actual result

    Could not cast value of type 'NSNull' (0x1150485b8) to 'webview_flutter_wkwebview.AuthenticationChallengeResponse' (0x102d7eed0).
    Lost connection to device.
    

    Crashing thread (Runner-2026-08-19-093456.ips, EXC_CRASH / SIGABRT):

    libswiftCore.dylib   swift::swift_dynamicCastFailure(...)
    libswiftCore.dylib   swift_dynamicCast
    Runner.debug.dylib   closure #1 in PigeonApiWKNavigationDelegate.didReceiveAuthenticationChallenge(pigeonInstance:webView:challenge:completion:)
    Runner.debug.dylib   thunk for @escaping @callee_guaranteed @Sendable (@in_guaranteed Any?) -> ()
    Flutter              __48-[FlutterBasicMessageChannel sendMessage:reply:]_block_invoke
    

    Expected instead: a Pigeon error, or a fallback to performDefaultHandling — the else if listResponse[0] == nil branch right above the as! shows the intent, it just does not recognise NSNull.

    Version verification

    Reproducible on the latest stable:

    • Flutter 3.47.0 (stable, framework 4cf2416426, engine 5f77625673), webview_flutter 4.14.1 / webview_flutter_wkwebview 3.26.0, iOS 26.5 simulator (iPhone 17 Pro) and physical devices — sample above, crashes every run.
    • The production crashes we originally reported come from Flutter 3.44.9 with the same plugin version.

    Note the same message was reported in #162437 and fixed in 3.18.3 by introducing AuthenticationChallengeResponse.createAsync. That closed the "response was never registered natively" case, but not this one: the identifier is registered and then dropped natively, and the NSNull decode still force-casts. The same == nil guard followed by as! is emitted for every non-null return value in WebKitLibrary.g.swift, so AuthenticationChallengeResponse is just the easiest one to hit in the wild (it fires on ordinary https loads).

    flutter doctor -v

    Doctor output (Flutter 3.47.0, used for the reproduction)
    [✓] Flutter (Channel stable, 3.47.0, on macOS 26.5.2 25F84 darwin-arm64 (Rosetta), locale en-TR) [2.1s]
        • Flutter version 3.47.0 on channel stable at /Users/dev/sdks/flutter_3.47.0
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 4cf2416426 (8 days ago), 2026-08-11 11:53:49 -0700
        • Engine revision 5f77625673
        • Dart version 3.13.0
        • DevTools version 2.60.0
        • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, enable-record-use, enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [3.8s]
        • Android SDK at /Users/dev/Library/Android/sdk
        • Emulator version 37.1.11.0 (build_id 15917651) (CL:N/A)
        • Platform android-36.1, build-tools 36.1.0
        • Java binary at: /Users/dev/Library/Java/JavaVirtualMachines/temurin-24.0.2/Contents/Home/bin/java
          This JDK is specified in your Flutter configuration.
          To change the current JDK, run: `flutter config --jdk-dir="path/to/jdk"`.
        • Java version OpenJDK Runtime Environment Temurin-24.0.2+12 (build 24.0.2+12)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 26.6) [1,624ms]
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Build 17F113
        • CocoaPods version 1.16.2
    
    [✓] Chrome - develop for the web [25ms]
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Connected device (3 available) [44.1s]
        • iPhone 17 Pro (mobile) • 23F1B535-629A-4043-AC94-7D3EB773CF90 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-26-5 (simulator)
        • macOS (desktop)        • macos                                • darwin-arm64   • macOS 26.5.2 25F84 darwin-arm64 (Rosetta)
        • Chrome (web)           • chrome                               • web-javascript • Google Chrome 151.0.7922.138
    
    [✓] Network resources [806ms]
        • All expected network resources are available.
    
    • No issues found!
  4. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Aug 19, 2026
  5. suryaKommana2662 commented on Aug 19, 2026

    @suryaKommana2662
    Contributor

    @bayraktarmdkaraca Thanks for providing the minimal reproduction, crash stack, and flutter doctor -v output. I’ve reviewed the additional information and am routing this issue to the team-ios for further investigation.

  6. added
    platform-iosiOS applications specifically
    fyi-ecosystemFor the attention of Ecosystem team
    team-iosOwned by iOS platform team
    and removed
    in triagePresently being triaged by the triage team
    on Aug 19, 2026
  7. hellohuanlin commented on Aug 20, 2026

    @hellohuanlin
    Contributor

    @bayraktarmdkaraca it looks like you have already done the hard part of finding the root cause. PRs welcome.

  8. added
    P2Important issues not at the top of the work list
    on Aug 20, 2026
  9. 4 remaining items

  10. bparrishMines commented on Sep 15, 2026

    @bparrishMines
    Contributor

    The webview_flutter_wkwebview code generation still needs to be updated.

  11. added a commit that references this issue on Sep 15, 2026
    d8aba48
  12. AbdeMohlbi commented on Sep 16, 2026

    @AbdeMohlbi
    Member

    huh smth is wrong i did update the main branch on my fork and this caused the issue to get closed ?

  13. Xelorium commented on Sep 16, 2026

    @Xelorium

    29.0.2 isn't on pub.dev yet, so this can't land as a PR quite yet. I have the regeneration prepared and verified locally in the meantime — analyze clean, all 154 unit tests pass, the iOS example builds, and the didReceiveAuthenticationChallenge reply now goes through the NSNull check — though the diff is large since it regenerates from 27.3.2. Is updating the generated code something the team handles as part of the pigeon release, or would a PR from me be useful once it publishes?

  14. bparrishMines commented on Sep 16, 2026

    @bparrishMines
    Contributor

    It needed to publish manually due to #192709. It should be on pub now. @Xelorium its fine for you to generate it if you already have the PR ready.

  15. github-actions commented on Oct 13, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  16. locked as resolved and limited conversation to collaborators on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listp: webviewThe WebView pluginplatform-iosiOS applications specificallyteam-iosOwned by iOS platform teamtriaged-iosTriaged by iOS platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions