Home > Mobile, Software Engineering > Cookies in Hybrid Android Apps

Cookies in Hybrid Android Apps

01/17/2013

Sharing Cookies between WebViews and native code

Hybrid mobile applications require a close collaboration between native code and WebViews. Unlike iOS, Android doesn’t provide any implicit bridge between HTTP cookies in WebViews and native HTTP requests made through HttpConnection or Apache’s HttpClient interfaces. As a developer, it’s up to you to manually synchronize these systems if you want to share state or authentication context between them.

This is often a useful feature for integrating third party web services via their login page in your app’s WebView. Authentication credentials stored as HTTP cookies should be shared for subsequent HTTP calls, whether they’re native or a WebView.

Android Cookie Management and Persistence

Add CookieManager and CookieSyncManager hooks to properly setup your application to accept and save cookies, while writing them to disk periodically or when the application pauses or shuts down.

Application

public class YourApplication extends Application {
  public void onCreate() {
    super.onCreate();

    //Setup Cookie Manager and Persistence to disk
    CookieSyncManager.createInstance(this);
    CookieManager.getInstance().setAcceptCookie(true);
  }
}

Main Activity

public class MainActivity extends BaseActivity {
  public void onResume() {
    CookieSyncManager.getInstance().stopSync();
  }

   public void onPause() {
     CookieSyncManager.getInstance().sync();
   }
}

Synchronizing Android CookieManager to Apache HttpClient

Now that cookies have an application wide cookie store that will save WebView cookies and persist the cookie store to disk, we need to sync cookies from this store for use by native code.

Synchronize native HttpClient with App’s Cookie Store
This example shows populating an Apache CookieStore configured in an HttpClient that is pre-populated with any HTTP Cookies that apply to this URL. Then, after executing the request, writing all of the cookies saves in the CookieStore into the App’s Android CookieManager.

public static String getURLContentsAsString(String url) {

  DefaultHttpClient httpClient =  new DefaultHttpClient();

  //Injects App's CookieManager cookies for this URL into HttpClient CookieStore
  syncCookiesFromAppCookieManager(url, httpClient);

  HttpGet hp = new HttpGet(url);
  HttpResponse response = hc.execute(hp);

  //Save's cookies from HttpClient in App's CookieStore
  syncCookiesToAppCookieManager(url, httpClient);

  // Process Response here
  //...
}

syncCookiesFromAppCookieManager()
This helper function fetches all cookies for this URL from the Android CookieManager, which gives us a raw Cookie HTTP header value. The Apache CookieStore expects Cookie objects, which we will generate using the RFC2109Spec that matches the format of the Android CookieManager.

public static void syncCookiesFromAppCookieManager(String url, DefaultHttpClient httpClient) {

  BasicCookieStore cookieStore = new BasicCookieStore();
  httpClient.setCookieStore(cookieStore);

  CookieManager cookieManager = CookieManager.getInstance();
  if (cookieManager == null) return;

  RFC2109Spec cookieSpec = new RFC2109Spec();
  String rawCookieHeader = null;
  try {
    URL parsedURL = new URL(url);

    //Extract Set-Cookie header value from Android app CookieManager for this URL
    rawCookieHeader = cookieManager.getCookie(parsedURL.getHost());
    if (rawCookieHeader == null) return;

    //Convert Set-Cookie header value into Cookies w/in HttpClient CookieManager
    int port = parsedURL.getPort() == -1 ?
      parsedURL.getDefaultPort() : parsedURL.getPort();

    CookieOrigin cookieOrigin = new CookieOrigin( parsedURL.getHost(),
                                                  port,
                                                  "/",
                                                  false);
    List<Cookie> appCookies = cookieSpec.parse(
      new BasicHeader("set-cookie", rawCookieHeader),
      cookieOrigin);

    cookieStore.addCookies(appCookies.toArray(new Cookie[appCookies.size()]));
  } catch (MalformedURLException e) {
    // Handle Error
  } catch (MalformedCookieException e) {
    // Handle Error
  }
}

syncCookiesToAppCookieManager()
This helper extracts Cookie objects from the Apache CookieStore and converts them into Set-Cookie header strings for the Android CookieManager.

public static void syncCookiesToAppCookieManager(String url, DefaultHttpClient httpClient) {

  CookieStore clientCookieStore = httpClient.getCookieStore();
  List<Cookie> cookies  = clientCookieStore.getCookies();
  if (cookies.size() < 1) return;

  CookieSyncManager syncManager = CookieSyncManager.getInstance();
  CookieManager appCookieManager = CookieManager.getInstance();
  if (appCookieManager == null) return;

  //Extract any stored cookies for HttpClient CookieStore
  // Store this cookie header in Android app CookieManager
  for (Cookie cookie:cookies) {
    //HACK: Work around weird version-only cookies from cookie formatter.
    if (cookie.getName() == "$Version") break;

    String setCookieHeader = cookie.getName()+"="+cookie.getValue()+
      "; Domain="+cookie.getDomain();
    appCookieManager.setCookie(url, setCookieHeader);
  }

  //Sync CookieManager to disk if we added any cookies
  syncManager.sync();
}
About these ads
  1. Mark Parnell
    01/21/2013 at 9:58 pm | #1

    Thank you so much! I’ve been banging my head against the desk for several days over this, and thanks to this post it’s finally working!

  2. 03/01/2013 at 6:25 am | #2

    Thanks a million Winfield! I too spent nearly a week trying to figure this out and after scouring the web I finally found this post. I would have thought Android would make it a bit more obvious than this or at least provide a few more pointer in the right direction. Thanks again!

  3. jjfive
    03/14/2013 at 4:31 am | #3

    thanks!!

    if (cookie.getName() == “$Version”) break;
    modify to
    if(“$Version”.equals(cookie.getName())) break;

  4. gman
    05/22/2013 at 10:45 am | #4

    There is a significant bug in the code:

    if (cookie.getName() == “$Version”) break;

    Should be:

    if (cookie.getName().equals(“$Version”)) continue;

    The Android test suite I ran into shows that the $Version cookie appears first, so had the original code worked, it would have just ignored all cookies, except the code was broken so it went ahead and processed $Version along with everything else.

    Unfortunately, while this code does appear to work with the “Apache HTTP” Java stuff in Android (once you fix the bugs), as soon as you hit a SNI-capable web server, the basis for the code goes out the window as I discovered that Google no longer recommends using the Apache HTTP Java stuff and instead prefers HttpURLConnection for network communication. In addition, it requires fewer lines of code to sync cookies between android.webkit.CookieManager and HttpURLConnection.

  5. Kavita
    07/17/2013 at 9:25 am | #5

    Hi Thank you really.

    still not able to understand how to make second request with cookie to get authorized page.
    I mean, this function works syncCookiesFromAppCookieManager(URL, client); and I can see the O/p in Log

    But when I try to execute URL without credentials(after login) then again I get unauthorized error. I mean syncCookiesToAppCookieManager(URL, client); doent work.

    part of my code is like this

    StringBuilder stringBuilder = new StringBuilder();
    DefaultHttpClient client = new DefaultHttpClient();

    syncCookiesFromAppCookieManager(URL, client);

    HttpGet httpGet = new HttpGet(URL);

    try {
    HttpResponse response = client.execute(httpGet);
    StatusLine statusLine = response.getStatusLine();
    int statusCode = statusLine.getStatusCode();
    if (statusCode == 200) {
    HttpEntity entity = response.getEntity();

    syncCookiesToAppCookieManager(URL, client);

    InputStream content = entity.getContent();
    BufferedReader reader = new BufferedReader(
    new InputStreamReader(content));
    String line;

    while ((line = reader.readLine()) != null) {
    stringBuilder.append(line);
    }

  6. Tal
    08/05/2013 at 11:59 am | #6

    I’ve had a similar solution with sharing cookies between HttpURLConnection and WebViews. I solved it in a completely different way – without manual syncs. I tried to use the same cookie repository for both.. and it seems to be working. Posted this on Stackoverflow – http://stackoverflow.com/questions/18057624/two-way-sync-for-cookies-between-httpurlconnection-java-net-cookiemanager-and/

  7. 08/05/2013 at 12:43 pm | #7

    Hey Tal, that looks great. I tried doing something similar but hit a roadblock (I’ve since forgotten). It’s a much more elegant solution to use a single store or connect them dynamically.

  8. 04/01/2014 at 4:24 pm | #8

    Please Note: This code is severely broken! The Android CookieManager gives back a cookie list formatted according to RFC2109 section 4.3.4: http://tools.ietf.org/html/rfc2109#section-4.3.4, which is delimited _either_ by semi-colon or comma. However, in Android’s implementation, they return semi-colon delimited. The RFC2109Spec object expects them to be delimited by comma. Therefore, if there are multiple cookies for a particular domain, this code will break, and only transfer the first cookie in the list.

    • 04/01/2014 at 4:27 pm | #9

      Hi Mark, Thanks for posting this up here. Do you have updated code? I’m happy to post a fix in the original and give you credit.

Comments are closed.
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: