I have a Xamarin.Forms 5.0 application (for both iOS and Android). I am not sure if this is a Xamarin issue or just Android. On iOS it is working fine.
In the app I use a webview to display single page webapplication. Inside that spa I have a login page with a "remember me" checkbox. When this is checked, the backend creates persistent cookie instead of a sessioncookie. The login is done with a XHR request to the backend.
Seems all working fine, but when the app is restarted, it doesn't know the cookie anymore and the user has to login again.
When I do a full reload of the page in the webview (after login), it looks like the cookie is persisted. After restarting the app, the user is logged in automatically, so the cookie is available.
So the problem seems to be that new cookies in the response of XHR requests are not persisted, while the response cookies of a normal page request are.
Anybody any ideas about this?
Added some code
I created a simple Xamarin.Forms project with an Android app.
Added this in the MainPage.xaml:
<StackLayout>
<Button Clicked="Button_Clicked" Text="(Re)Load"></Button>
<WebView x:Name="wv"
WidthRequest="1000"
HeightRequest="1000"
></WebView>
</StackLayout>
And in the codebehind:
private void Button_Clicked(object sender, EventArgs e)
{
wv.Source = "https://---.---.nl/portal";
}
This loads a SPA webapplication. Not much code to show.
As i know, UWP and iOS would share their cookie containers automatically between the WebView and native http client, however Android does not.
The WebView could use CookieManager to handle cookies. If you want to share cookies between the two, you will have to manually process the information. You need to know the Uri of the domain for these cookies.
private void CopyCookies(HttpResponseMessage result, Uri uri)
{
foreach (var header in result.Headers)
if (header.Key.ToLower() == "set-cookie")
foreach (var value in header.Value)
_cookieManager.SetCookie($"{uri.Scheme}://{uri.Host}", value);
foreach (var cookie in GetAllCookies(_cookieContainer))
_cookieManager.SetCookie(cookie.Domain, cookie.ToString());
}
public void ReverseCopyCookies(Uri uri)
{
var cookie = _cookieManager.GetCookie($"{uri.Scheme}://{uri.Host}");
if (!string.IsNullOrEmpty(cookie))
_cookieContainer.SetCookies(new Uri($"{uri.Scheme}://{uri.Host}"), cookie);
}
It is not very clear, at least for me, when the WebView flushes the cookies and when not.
The simple solution for me was to flush the cookies everytime the onPause lifecycle event of the activity containing the webview is called. This occurs when another activity is started, or the user switches to another app or closes the app.
protected override void OnPause()
{
base.OnPause();
Android.Webkit.CookieManager.Instance.Flush();
}
Related
I'm creating a simple webview app using nativescript-vue where there is nothing else in the app except the webview that loads a website fullscreen.
On iOS it works great, I can log in to the website turn the app off and on and I'm still logged in.
On Android, the cookies are not saved and you have to log in again every time you turn the app off.
Any way I can enable cookies in the webview for Android?
I couldn't make it work with cookies, so I had to switch and use localstorage as a backup for saving tokens instead of cookies.
Here is how my final version looks:
<template>
<Page>
<WebView #loadStarted="onLoadStarted"
#loaded="onWebViewLoaded"
src="https://yoururl.com/"
/>
</Page>
</template>
<script>
import { isAndroid } from "tns-core-modules/platform"
export default {
methods: {
onLoadStarted (webargs) {
if (isAndroid) {
const webView = webargs.object;
webView.android.getSettings().setDomStorageEnabled(true) // This will enable local storage
}
},
onWebViewLoaded(webargs) { // I use this only to disable the default zoom buttons
if (isAndroid) {
const webView = webargs.object;
webView.android.getSettings().setDisplayZoomControls(false)
webView.android.getSettings().setBuiltInZoomControls(false)
}
},
}
};
</script>
Apparently, you should be able to enable cookies doing this (at least for newer SDK users):
webView.on(WebView.loadStartedEvent, (args) => {
webView.android.getSettings().setAcceptThirdPartyCookies(webView, true)
});
But that didn't work as expected as it seemed like sometimes it would save cookies and sometimes it won't or sometimes it would remove cookies (when you log out) and sometimes not.
I hope this info helps someone as it sure seems there is little to none info about this especially if you aren't much familiar with pure Java Android development.
This code should work
const webView = webargs.object;
if (android.os.Build.VERSION.SDK_INT >= 21) {
android.webkit.CookieManager.getInstance().setAcceptThirdPartyCookies(webView.android, true);
}
I'm making an iOS app, and I'm using an UIWebView to show a site, and everything seems to be working fine.
But when I go to a page of this site that require HTTP Authentication (Basic/NTLM Auth, HTTP) it does not work fine.
I was reading and I found a method didReceiveAuthenticationChallenge that indicate when is necessary Authentication in a page.
I found a example provided by Apple (but it does not work for me). https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html
-(void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge previousFailureCount] == 0) {
NSURLCredential *newCredential;
newCredential = [NSURLCredential credentialWithUser:[self preferencesName]
password:[self preferencesPassword]
persistence:NSURLCredentialPersistenceNone];
[[challenge sender] useCredential:newCredential
forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
// inform the user that the user name and password
// in the preferences are incorrect
[self showPreferencesCredentialsAreIncorrectPanel:self];
}
}
I was doing the same testing In Android and I found that We can use this method (It works for me):
#Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
handler.proceed(username, password);
...
}
My question is, What is the correctly way to do HTTP Authentication (Basic Auth, HTTP) in objective-C ?
I did a test with two apps(android and IOS) for the same WebSite so Android app works fine, but the iOS app not.
Any advice will be use full for me!
Thanks.
I'm trying to access an image protected by a simple http basic authentication mechanism. This example works fine when using my browser
...
var mImage = sap.m.Image("Im1");
mImage.setSrc("http://user:password#192.168.0.100/image.jpg");
...
var page = new sap.m.Page({
title: "Image",
content: mImage
}
Yet when I wrap it in a Cordova container (Android) this simple way of attaching user+pw does not seem to work. The webserver responds with a 401 error and my app does not send an authentication header.
The next step I've tried was to send a XmlHttRequest before trying to access time image URL:
var xhr =new XMLHttpRequest();
xhr.open("GET", "http://192.168.0.100/image.jpg", true, "user", "password");
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
alert(xhr.responseText); //returns the image in text form i.e. authentication works fine
mImage.setSrc("http://192.168.0.100/image.jpg") //returns a 401
}
};
xhr.send(null);
Yet another failure. I was under the impression that once I am authenticated I will get a session. But apparently the xhr session and the sap.m.image session seem to be apart.
Any thoughts on how to tackle this issue
I found out that this appears to be a bug in the Android Webview. It is fixed in Android 5- This is fine for me and my example above works fine
So I've been trying to get a WebView to work properly to authenticate a user session that is used in app. It works on all 4.0+ devices but when I try to use the same process on a 2.3.6 and 2.3.7 devices it throws this exception in the log:
com.myapp.WebActivity E/webkit﹕ parse cookie failed for: request_uri=xxxxxxxx; path=/; expires=Mon, 16-Jun-2014 00:00:00; domain=;
It doesn't crash the app but will not save this one cookie needed to properly authenticate the users session. I've looked into the Android Source code and it appears that when trying to save this specific cookie there is a RunTimeException that is happening. Here is the Android Source where the exception is thrown and caught. I'm not 100% sure where to look next since the cookies are saved properly on 4.0+ and it seems like a AOSP bug. Also, after the WebView loads the CookieManager doesn't have the cookie that it threw the exception on but has others.
So my real question is: Is there anyway to manually get a cookie returned from a WebView page load or can I get the cookie to be saved by the Android 2.3 WebView automatically somehow?
Thanks in advance.
Here is what I use to check and save a cookie that is picked up by a subsequent WebView. It works on Android 2.2 for sure, as well as Android 4.X.
Checking:
// see if the cookie is already set up
boolean cookieAlreadySetUp = false;
String cookie = cookieManager.getCookie(urlHost);
if ( cookie != null && cookie.length() > 0 ) {
String[] cookies = cookie.split(";");
for ( int i = 0; i < cookies.length; i++ ) {
String oneCookie = cookies[i];
if ( oneCookie.trim().equals(SharedData.COOKIE_STRING) ) {
cookieAlreadySetUp = true;
break;
}
}
}
Saving:
cookieManager.setAcceptCookie(true);
cookieManager.setCookie(urlHost, myCookieString);
CookieSyncManager.getInstance().sync();
// give time for the cookie to become known since the save process is asynchronous to this thread
SystemClock.sleep(100);
Be aware of the asynchronous nature of the cookie manager, both for adding or removing a cookie, as well as testing its existence.
While creating an Android app in Appcelerator's Titanium that involves both webView and background calls, I ran into a problem / bug where the cookies were getting corrupted on multiple createHTTPClient calls.
Cookies were originally obtained from the webView:
var webview = Ti.UI.createWebView();
webview.url = 'http://www.example.com';
window.add(webview);
webview.addEventListener('load', function(e) {
cookies = e.source.evalJS("document.cookie");
Titanium.App.Properties.setString('cookies',cookies);
}
window.open({modal:true});
and then later used with a background call:
var loader = Titanium.Network.createHTTPClient();
loader.open("GET",base_url + url);
loader.onload = function() {
// process response
}
loader.setRequestHeader('Cookie',Titanium.App.Properties.getString("cookies"));
loader.send();
The first time the above createHTTPClient chunk of code was called, everything worked, but subsequent runs of the above code would send corrupted cookies. In Google App Engine (gae), printing out the request headers would look like this (broken):
logging.info('Request:\n%s' % self.request)
broken response (only the cookie portion of the request header is shown)
Cookie: auth="eyJfdXNlciI6WzYsMSwiVmRoZEtnYWZRMnNxazFjaVM0U1lKdCIsMTM1NzIyMzcwOSwxMzU3MjIzNzA5XX0\075|1357223709|4f622167f477a8c82cab196af4b0029af1a966d7", auth=eyJfdXNlciI6WzYsMSwiVmRoZEtnYWZRMnNxazFjaVM0U1lKdCIsMTM1NzIyMzcwOSwxMzU3MjIzNzA5XX0\075|1357225569|7a469fab7a38a437649c25620729e07c4607f617
Cookie2: $Version=1
working response
Cookie: auth="eyJfdXNlciI6WzYsMSwiVmRoZEtnYWZRMnNxazFjaVM0U1lKdCIsMTM1NzIyMzcwOSwxMzU3MjIzNzA5XX0\075|1357223709|4f622167f477a8c82cab196af4b0029af1a966d7"
...
I suspect the issue has something to do with unicode characters or something inside createHTTPClient. Two auth= statements are shown in the corrupted cookie.
In summary, when the app first launches, the background Titanium.Network.createHTTPClient call works, and any calls after that appear to send corrupted cookies.
The HTTPClient documentation says "object is intended to be used for a single request," so I assumed everything would reset after multiple calls. But something was different after the first call.
Adding loader.clearCookies(base_url); to the code before setting the cookies seems to fix the issue.
var loader = Titanium.Network.createHTTPClient();
loader.open("GET",base_url + url);
loader.onload = function() {
// process response
}
loader.clearCookies(base_url); // THE FIX.
loader.setRequestHeader('Cookie',Titanium.App.Properties.getString("cookies"));
loader.send();