Flutter InAppWebView - Issue passing auth tokens as cookies on Android devices

63 views Asked by At

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!

0

There are 0 answers