Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issues with ASP.NET Forms Authentication on Phonegap (Android)

Tags:

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.

like image 955
balzafin Avatar asked Aug 17 '15 13:08

balzafin


1 Answers

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

  1. Redirect to a login page hosted at your website say http://domain.com/api/login
  2. After successful login, use PhoneGap localStorage (not browser's localStorage) to store authorization.
  3. 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.
  4. Setup a Authorization module, where you can manually authorize asp.net request if your authorization was sent through custom header in http request
like image 76
Akash Kava Avatar answered Sep 29 '22 07:09

Akash Kava