Is there any way to intercept redirect requests in Android WebView? shouldInterceptRequest(WebView,WebResourceRequest) does not seem to get called!
I wanted to automatically catch token expiration in all web view requests by:
Issuing a redirect rather than a 401 from the server
Using shouldOverrideUrlLoading() to invoke AppAuth where a login is necessary
Using shouldInterceptRequest to bounce back to the original URL with a refreshed token where a login is not necessary
Item 2 is working fine, but item 3 is failing spectacularly since shouldInterceptRequest seems not to be called for redirects, which seems really wrong -- particularly since this is not documented and the WebResourceRequest API would lead one to believe that one can even check whether the request is a redirect.
I'd happily respond to 401's instead of using redirects -- but I see no way to retry the request "in situ" with an updated token, unless the request happens to be a top-level page request.
I suppose I could try the old "in page" redirect instead of a 302 to see if that works any better, but even if it does that is really a hack.
(Note that this is clearly a different issue than Android WebView, how to handle redirects in app instead of opening a browser -- as I already have a webview and am trying to intercept and manipulate redirection requests.)
Example of interception on Kotlin:
webView.webViewClient = object: WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
if (url.startsWith("https://example.com")) {
// ...
return false
}
return super.shouldOverrideUrlLoading(view, url)
}
}
Related
How can I make a WebView request the CSS and javascript links when I load HTML externally into the WebView?
This question is similar to:
https://stackoverflow.com/questions/34608714/css-and-js-files-not-loading-in-android-webview
but we are loading everything from a server. In order to support SSL Pinning we override the
loadUrl(String url)
method (and will override the others) and handle the request with an HttpClient.execute call. When we receive the data back from the network, then we push it into the webview with
WebView.loadData(event.getContent(), event.getMimeType(), event.getEncoding());
How should we implement this design to retrieve data externally and then load it into the webview? The approach above shows the HTML fine but the CSS files are not loaded.
You don't. Use the loadDataWithBaseURL. Then the WebView will fire requests to load the css/js/png. The tricky part is you can override WebView.loadUrl for the initial request but then override WebViewClient.shouldInterceptRequest for each of the css/js/png requests. In there you can fire your network request and return the response as a stream (WebResourceResponse).
I have an ASP.NET MVC/Web API backend where I have implemented a Forms Authentication for my Phonegap app. The login is executed by sending the users credentials via jQuery Ajax call like this:
$.ajax({
type: "POST",
url: "/api/authentication/login",
data: JSON.stringify({ Username: username, Password: password }),
contentType: "application/json; charset=utf-8",
dataType: "TEXT",
statusCode: {
200: function (response, status, xhr) {
// successfully authenticated
Backbone.history.navigate("/", { trigger: true });
}
}
});
The backends login method looks like this:
[ActionName("login")]
[AllowAnonymous]
public LoginResult Login(LoginCredentials credentials)
{
// doing all kinds of things here
// if valid credentials
FormsAuthentication.SetAuthCookie(loginID, true);
return loginResult;
}
I have this in my Web.config:
<authentication mode="Forms">
<forms
name=".ASPXAUTH"
loginUrl="/login"
defaultUrl="/home"
protection="All"
slidingExpiration="true"
timeout="525600"
cookieless="UseCookies"
enableCrossAppRedirects="false"
requireSSL="true"
>
</forms>
</authentication>
Now the problem with Android here is that the cookie is properly set and it does work on my authorized methods after the login, but sometimes (often) when I close the app and open it again, I'm no longer logged in. The cookie isn't there anymore, I can not see it in the request. This should not happen because I have set the timeout to 525600. I have noticed that this problem often occurs when I close the app immediately after login. In other hand if I log out and then log in without closing the app, the cookie is saved properly.
But, if I get the cookie to stick, most of the time the logout behaves strangely as well. This is how I do the logout request:
$.ajax({
type: "POST",
url: "/api/authentication/logout",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "text"
success: function (response) {
// successfully logged out
Backbone.history.navigate("api/login", { trigger: true });
}
});
The backend:
[ActionName("logout")]
[AllowAnonymous]
public String Logout()
{
FormsAuthentication.SignOut();
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, "");
cookie.Expires = DateTime.Now.AddYears(-1);
HttpContext.Current.Response.Cookies.Add(cookie);
return "home";
}
Now similar to the problem with the login, the logout first seems to be successful and the cookie is no longer sent with any requests. But when I close the app and open it again, the cookie is back and I'm logged in again. I can see that the cookie has the same value as the one I thought I just removed by setting its expiration time to the past.
I have tried all kinds of tricks, like:
extra reloads after the login/logout (location.reload())
executing the logout/login request multiple times
executing request to other methods after the login/logout
1-10 second timeout between the login/logout request and the reload
all kinds of variations of the above
The authentication works as intended on iOS and Windows Phone. The problem occurs only on Android (tested on KitKat and Lollipop). No problem on the Android emulator, but on real devices and Visual Studios Android emulator this happens all the time.
I don't know in which direction to go from here. Is there something in the Android WebView that could cause this kind of behavior? Is there something else I could test out? Please help!
I'm more than happy to give more information if needed.
EDIT:
Inspired by Fabian's comment, I changed the logout method to this:
FormsAuthentication.SignOut();
HttpCookie cookie = HttpContext.Current.Response.Cookies[FormsAuthentication.FormsCookieName];
cookie.Expires = DateTime.Now.AddYears(-1);
HttpContext.Current.Response.Cookies.Clear();
HttpContext.Current.Response.Cookies.Add(cookie);
return "home";
Instead of creating a new cookie, I used the one in the response. It did not work.
I also tried something I found from here: http://techblog.dorogin.com/2013/01/formsauthentication-gotcha-with-signout.html That also did no difference, the path was not the problem. Still looking for a solution.
ANOTHER EDIT:
Still not able to find a solution for this. I had to make a horrible workaround.
Login: I make two reloads after the login and then a request to
a dummy method. This seems to work every time.
Logout: I use a flag placed in localStorage to determine if the user has logged out and perform a logout in the startup. This always removes the cookie correctly.
I'm not happy with these hacks and I'm still hoping for a better solution.
PhoneGap loads files from file:// protocol. Unfortunately, cross origin requests are not allowed and unless you open cross origin requests from all hosts *, this problem will not resolve.
There are multiple ways this can be fixed but they are really long.
Load Html from http://
Load entire website from web server instead of local storage. This removes all issues with cross origin requests. Benefit is you don't need to publish new version of app when you change UI. But you will have to implement very powerful caching and first time opening app will take longer time.
Intercept http:// and deliver local files
As you know, phonegap simply uses WebView, in all platforms, you can simply override Url protocol to inject files from your app's local storage. This will be faster, and browser will think that it is loading html from same resource.
Setup OAuth + custom header for authentication
Redirect to a login page hosted at your website say http://domain.com/api/login
After successful login, use PhoneGap localStorage (not browser's localStorage) to store authorization.
Navigate to your local html pages from app and for each json api request you send to server, send authorization header as separate header in ajax request.
Setup a Authorization module, where you can manually authorize asp.net request if your authorization was sent through custom header in http request
I believe I have found the solution. The Phonegap version on your config.xml file is cli-5.1.1, which includes Android Phonegap version 4.0.2 according to the documentation.
The problem with the versions is it seems the Android Phonegap team eventually fixed the cookie storage problem on version 5.2.0. It can be found in release notes as:
CB-10896 We never enabled cookies on the WebView proper
Therefore, updating your Phonegap to latest version should solve the problem.
According to MSDN:
The FormsAuthentication.SignOut method removes the
forms-authentication ticket information from the cookie.
And that's all you need to log the user out. You don't need to expire or remove your cookie itself. Simply change your Logout() to:
[ActionName("logout")]
[AllowAnonymous]
public String Logout()
{
FormsAuthentication.SignOut();
return "home";
}
I don't have much experience with development in Android. I am working on app, which can communicate with Facebook, Twitter, LinkedIn and Google+.
Every social network has own wrapper, which handles communication between Android app and social network. Facebook communication is already done. I have problems with Twitter communication. Implemetations, which I have found on internet use twitter4j library and internet browser for logging. Then you must read response data from browser.
For example this: Twitter Test App
My issue is reading this response data from browser. Logging and authorizing in browser goes without problems. Tutorials like I mentioned above use usually onNewIntent() Activity method for dealing with browser response. But I cannot use this method, because it is used for different purposes. I don't know if it's posible to do this without any other Activity apart from MainActivity or I need to create another Activity in wrapper, which will handle whole Twitter communication.
Thanks for any help.
I'm really not sure why so many examples for twitter4j on the internet go through intents and custom schemes to handle the callback URL. I find this awkward for most scenarios. I also dislike leaving the application to go to the browser to login.
Instead, try using a WebView to load the page. Then you can attach a WebViewClient which can detect the callback URL. You won't have to deal with changing the manifest, etc. too. You might do something like this, perhaps inside onCreateView in a DialogFragment:
mWebView = new WebView(context);
mWebView.setWebViewClient(new WebViewClient() {
#Override
public void onLoadResource(WebView view, String url) {
if (url.startsWith(CALLBACK_URL)) {
Uri uri = Uri.parse(url);
String denied = uri.getQueryParameter("denied");
if (denied != null) {
// handle denied
} else {
// handle authenticated
}
}
super.onLoadResource(view, url);
}
});
and then you can load the authentication URL in onViewCreated (if you're doing this in a DialogFragment):
Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
RequestToken reqToken = twitter.getOAuthRequestToken();
mWebView.loadUrl(reqToken.getAuthenticationURL());
I'm using FB's oauth to successfully authenticate users, but I'm having trouble reliably logging them out using what seems to be the recommended approach: FB.logout using the FB JDK.
Specifically, calling FB.logout (after loading the FB JDK and calling FB.init) successfully revokes the FB authentication granted during oauth if I'm using Chrome or Firefox. However, it does not seems to work in IE or in the Android browser. When I say it doesn't work in IE and Android browser, what I mean is that after calling FB.logout subsequent redirects to www.facebook.com/dialog/oauth.. load my callback page without prompting the user to enter credentials again. In Chrome and Firefox, they are 'correctly' prompted to do so at that point.
My various attempts to debug has led me to realize that calls to FB.getLoginStatus (even after calling FB.logout) will pass a valid response.session object to its callback in every browser. So, according to FB.getLoginStatus, the session is still active in every browser even after FB.logout. This makes me wonder my FB.logout works at all.
I know that oauth and the FB JDK are two different things and are not necessarily meant to play together nicely but I haven't seen any other alternatives to ending an FB oauth session, aside from calling FB.logout.
Anybody else seen these irregularities or have a solution? Thanks.
I found this answer after I posted the above:
FB.logout not working in IE8
It recommends putting the redirect after calling FB.logout in a setTimeout function. Basically, give the FB.logout function about 2000 ms to finish.
I tried it, and it fixed the problem in both IE and the Android browser. Basically, Chrome and Firefox have a fast enough JavaScript engine that FB.logout will finish executing before the new page loads in the browser, while IE and Android browser do not.
Following that logic, I realized that there is a reason FB.logout has a callback function and it is probably safer to use that to do any redirects (as opposed to playing with a setTimeout delay).
function mysignout()
{
FB.logout(function()
{
top.location.href = '../mobile.php'
});
}
Out of curiosity, I tested to see what is the timing difference on executing the callback in Chrome vs. IE. Chrome took 2511 ms to complete the logout IE took 3517 ms. I think what confused me is that I figured FB.logout just deleted the cookie locally and would take no time at all. Instead, it looks like it is some kind of ajax call to revoke authentication on the server and it takes a considerable bit of time.
Michael
Using your method you need to append a user access_token to your logout link. OR
Using the Login buttons auto logout link feature. autologoutlink='true'
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '135669679827333',
status : true, // check login status
cookie : true, // enable cookies to allow the server to access the session
xfbml : true, // parse XFBML
//channelUrl : 'http://WWW.MYDOMAIN.COM/channel.html', // channel.html file
oauth : true // enable OAuth 2.0
});
// redirect user on login
FB.Event.subscribe('auth.login', function(response) {
top.location.href = 'http://example.com/loggedin/';
});
// redirect user on logout.
FB.Event.subscribe('auth.logout', function(response) {
top.location.href = "http://example.com/loggedout/";
});
};
(function() {
var e = document.createElement('script'); e.async = true;
e.src = document.location.protocol +
'//connect.facebook.net/en_US/all.js';
document.getElementById('fb-root').appendChild(e);
}());
</script>
<fb:login-button autologoutlink='true' scope='email,publish_stream'></fb:login-button>
If you don't want to redirect the user after logout, or if you want to get sure, that the FB Session is clear on the client side, you can cleanup manually with these three commands:
FB._authResponse = null;
FB._userStatus = null;
document.cookie = 'fbsr_' + FB._apiKey + '=;'; // clear the facebook cookie
They can even be executed without FB.logout(), but in this case, the user is not logged out from facebook, so after a refresh, he will be logged in again.
Here's what I want to have figured out : I'm using a webview to access a feature of a site that requires authentication. Therefore whenever that particular link is loaded the login page is displayed . Is is possible to somehow authenticate silently so that the particular feature is displayed directly ? Perhaps by using the "webView.setHttpAuthUsernamePassword" (which does not seem to work for me, or I don't do it right) or by making some POST or GET before I load the page , or other possibility ?
On logging in the server is simply supposed to send me a cookie.
That method only works for HTTP Basic Auth, not for form-based login.
Your best bet would be to use the onLoadResource method of the WebViewClient to detect that the page is about to load, then override the content of the WebView using loadData with a chunk of HTML/js that posts to the login form with the proper parameters, then detect the successful cookie return and forward on to the original url.
Depending on the site it may be messy: you may have to set the proper referer headers or CSRF tokens and other things of that nature.
Good luck.
See, you could do the task this way, I am not sure if this is what you want:
#Override
public void onReceivedHttpAuthRequest(
WebView view,
HttpAuthHandler handler,
String host,
String realm) {
String currentUrl = yourWebView.getUrl();
if(currentUrl.equals("www.yourwebsite.com")){
handler.proceed("username","password");
}
}