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!