pichillilorenzo / flutter_inappwebview

A Flutter plugin that allows you to add an inline webview, to use a headless webview, and to open an in-app browser window.
https://inappwebview.dev
Apache License 2.0
3.29k stars 1.62k forks source link

Issues passing authentication cookies on Android #2009

Closed FKruegerCofinpro closed 9 months ago

FKruegerCofinpro commented 9 months ago

Environment

Technology Version
Flutter version 3.16.8
Plugin version ^6.0.0
Android version All Android devices -> minSdk 19

Description

In my Flutter app I try to authenticate the user for the transition between my app and my web app via setting the oauth accessToken and refreshToken as cookies. The approaches I tried were to implement this using the CookieManager and also by executing JavaScript in the "initialUserScripts". Both lead to the same issue. (See expected and current behaviour)

Also I am printing the cookies after I set them and they seem to be present.

Approach using CookieManager:

class WebViewDetailScreenArguments extends Equatable {
  final String title;
  final String uri;
  final bool isServiceBaseUri;

  const WebViewDetailScreenArguments({
    required this.uri,
    this.title = '',
    this.isServiceBaseUri = false,
  });

  @override
  bool get stringify => true;

  @override
  List<Object> get props => [title, uri, isServiceBaseUri];
}

class WebViewDetailScreenWrapperProvider extends StatelessWidget {
  final WebViewDetailScreenArguments arguments;

  const WebViewDetailScreenWrapperProvider({super.key, required this.arguments});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) =>
          WebviewCubit()..initWithAuth(path: arguments.uri, isServiceBaseUri: arguments.isServiceBaseUri),
      child: WebViewDetailScreen(
        title: arguments.title,
      ),
    );
  }
}

class WebViewDetailScreen extends StatefulWidget {
  final String title;

  const WebViewDetailScreen({
    super.key,
    required this.title,
  });

  @override
  State<WebViewDetailScreen> createState() => _WebViewDetailScreenState();
}

class _WebViewDetailScreenState extends State<WebViewDetailScreen> {
  @override
  Widget build(BuildContext context) {
    final GlobalKey webViewKey = GlobalKey();
    CookieManager _cookieManager = CookieManager.instance();

    final InAppWebViewSettings settings = InAppWebViewSettings(
      isInspectable: kDebugMode,
      thirdPartyCookiesEnabled: true,
      javaScriptEnabled: true,
      sharedCookiesEnabled: true,
    );

    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: TitleAppBar(
        isBackEnabled: true,
        isDarkActionBar: false,
        centerItem: Text(
          widget.title,
          style: context.textStyles.h5.semiBold.onContainer,
        ),
        onBackPressedCallback: () => Navigator.pop(context),
        leadingText: context.i18n.tooltipBackText,
        tooltipBackText: context.i18n.tooltipBackText,
        componentsConfig: context.componentStyles,
        assetConfig: context.assets,
      ),
      body: BlocBuilder<WebviewCubit, WebviewState>(
        builder: (context, state) {
          if (state is WebviewLoaded) {
            return InAppWebView(
              key: webViewKey,
              initialUrlRequest: URLRequest(url: WebUri(state.uri), httpShouldHandleCookies: true),
              initialSettings: settings,
              onWebViewCreated: (InAppWebViewController controller) async {
                /// Deleting the cookies or not here does not make a difference
                await _cookieManager.deleteCookies(url: WebUri(state.uri));

                await _cookieManager.setCookie(
                    url: WebUri(state.uri),
                    name: 'accessToken',
                    domain: WebUri(state.uri).host, /// I tried hardcoding all URIs and domains with no luck
                    isSecure: true,
                    value: state.accessToken);
                await _cookieManager.setCookie(
                    url: WebUri(state.uri),
                    domain: WebUri(state.uri).host,
                    name: 'refreshToken',
                    isSecure: true,
                    value: state.refreshToken);
                await _cookieManager.setCookie(
                    url: WebUri(state.logoutUri),
                    domain: WebUri(state.uri).host,
                    name: 'logoutRedirectUri',
                    isSecure: true,
                    value: state.refreshToken);

                final accessCookie = await _cookieManager.getCookie(url: WebUri(state.uri), name: 'accessToken');
                final refreshCookie = await _cookieManager.getCookie(url: WebUri(state.uri), name: 'refreshToken');
                print(accessCookie);
                print(refreshCookie);
              },
            );
          } else if (state is WebviewFailure) {
            return Placeholder();
          } else {
            return AppBackgroundContainer(
              colorConfig: context.colorsWatch,
              child: Center(
                child: CircularProgressIndicator(),
              ),
            );
          }
        },
      ),
    );
  }
}

Approach using JavaScript:

class WebViewDetailScreenArguments extends Equatable {
  final String title;
  final String uri;
  final bool isServiceBaseUri;

  const WebViewDetailScreenArguments({
    required this.uri,
    this.title = '',
    this.isServiceBaseUri = false,
  });

  @override
  bool get stringify => true;

  @override
  List<Object> get props => [title, uri, isServiceBaseUri];
}

class WebViewDetailScreenWrapperProvider extends StatelessWidget {
  final WebViewDetailScreenArguments arguments;

  const WebViewDetailScreenWrapperProvider(
      {super.key, required this.arguments});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => WebviewCubit()
        ..initWithAuth(
            path: arguments.uri, isServiceBaseUri: arguments.isServiceBaseUri),
      child: WebViewDetailScreen(
        title: arguments.title,
      ),
    );
  }
}

class WebViewDetailScreen extends StatefulWidget {
  final String title;

  const WebViewDetailScreen({
    super.key,
    required this.title,
  });

  @override
  State<WebViewDetailScreen> createState() => _WebViewDetailScreenState();
}

class _WebViewDetailScreenState extends State<WebViewDetailScreen> {
  @override
  Widget build(BuildContext context) {
    final GlobalKey webViewKey = GlobalKey();

    InAppWebViewSettings settings = InAppWebViewSettings(
      isInspectable: kDebugMode,
      thirdPartyCookiesEnabled: true,
      javaScriptEnabled: true,
      sharedCookiesEnabled: true,
    );

    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: TitleAppBar(
        isBackEnabled: true,
        isDarkActionBar: false,
        centerItem: Text(
          widget.title,
          style: context.textStyles.h5.semiBold.onContainer,
        ),
        onBackPressedCallback: () => Navigator.pop(context),
        leadingText: context.i18n.tooltipBackText,
        tooltipBackText: context.i18n.tooltipBackText,
        componentsConfig: context.componentStyles,
        assetConfig: context.assets,
      ),
      body: BlocBuilder<WebviewCubit, WebviewState>(
        builder: (context, state) {
          if (state is WebviewLoaded) {
            return InAppWebView(
              key: webViewKey,
              initialUrlRequest: URLRequest(
                  url: WebUri(state.uri), httpShouldHandleCookies: true),
              initialSettings: settings,
              initialUserScripts: UnmodifiableListView<UserScript>([
                UserScript(
                    source:
                        'document.cookie="accessToken=${state.accessToken}";',
                    injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START),
                UserScript(
                    source:
                        'document.cookie="refreshToken=${state.refreshToken}";',
                    injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START),
                UserScript(
                    source:
                        'document.cookie="logoutRedirectUri=${state.logoutUri}";',
                    injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START),
                UserScript(
                    source: 'console.log(document.cookie)";',
                    injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START),
              ]),
            );
          } else if (state is WebviewFailure) {
            return Placeholder();
          } else {
            return AppBackgroundContainer(
              colorConfig: context.colorsWatch,
              child: Center(
                child: CircularProgressIndicator(),
              ),
            );
          }
        },
      ),
    );
  }
}

Expected behavior: The user is authenticated when entering the Angular frontend via the InAppWebView both on iOS and Android devices.

Current behavior: The user is authenticated when entering the Angular frontend via the InAppWebView on iOS devices. On Android devices, the user is prompted to log in.

I hope you can help. Thanks!

github-actions[bot] commented 9 months ago

👋 @FKruegerCofinpro

NOTE: This comment is auto-generated.

Are you sure you have already searched for the same problem?

Some people open new issues but they didn't search for something similar or for the same issue. Please, search for it using the GitHub issue search box or on the official inappwebview.dev website, or, also, using Google, StackOverflow, etc. before posting a new one. You may already find an answer to your problem!

If this is really a new issue, then thank you for raising it. I will investigate it and get back to you as soon as possible. Please, make sure you have given me as much context as possible! Also, if you didn't already, post a code example that can replicate this issue.

In the meantime, you can already search for some possible solutions online! Because this plugin uses native WebView, you can search online for the same issue adding android WebView [MY ERROR HERE] or ios WKWebView [MY ERROR HERE] keywords.

Following these steps can save you, me, and other people a lot of time, thanks!

github-actions[bot] commented 1 month ago

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 and a minimal reproduction of the issue.