Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Cookie jar that handles syncing okhttp cookies with Webview cookie manager
import android.webkit.CookieManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
/**
* Provides a synchronization point between the webview cookie store and okhttp3.OkHttpClient cookie store
*/
public final class WebviewCookieHandler implements CookieJar {
private CookieManager webviewCookieManager = CookieManager.getInstance();
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
String urlString = url.toString();
for (Cookie cookie : cookies) {
webviewCookieManager.setCookie(urlString, cookie.toString());
}
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
String urlString = url.toString();
String cookiesString = webviewCookieManager.getCookie(urlString);
if (cookiesString != null && !cookiesString.isEmpty()) {
//We can split on the ';' char as the cookie manager only returns cookies
//that match the url and haven't expired, so the cookie attributes aren't included
String[] cookieHeaders = cookiesString.split(";");
List<Cookie> cookies = new ArrayList<>(cookieHeaders.length);
for (String header : cookieHeaders) {
cookies.add(Cookie.parse(url, header));
}
return cookies;
}
return Collections.emptyList();
}
}
@justinthomas-syncbak

This comment has been minimized.

Show comment Hide comment
@justinthomas-syncbak

justinthomas-syncbak Nov 15, 2016

Usage:

//import okhttp3.OkHttpClient;

OkHttpClient client = new OkHttpClient.Builder()
                .cookieJar(new WebviewCookieHandler())
                .build();
Owner

justinthomas-syncbak commented Nov 15, 2016

Usage:

//import okhttp3.OkHttpClient;

OkHttpClient client = new OkHttpClient.Builder()
                .cookieJar(new WebviewCookieHandler())
                .build();
@JosephM3388

This comment has been minimized.

Show comment Hide comment
@JosephM3388

JosephM3388 Aug 28, 2017

Will this send the cookies obtained from the okhttp client over to the webview?

Will this send the cookies obtained from the okhttp client over to the webview?

@justinthomas-syncbak

This comment has been minimized.

Show comment Hide comment
@justinthomas-syncbak

justinthomas-syncbak Sep 13, 2017

@JosephM3388, yes.

android.webkit.CookieManager is used by the WebView for cookie storage. We tie the okhttp client into it, so cookies are saved to it as well as read from it.

@JosephM3388, yes.

android.webkit.CookieManager is used by the WebView for cookie storage. We tie the okhttp client into it, so cookies are saved to it as well as read from it.

@JosephM3388

This comment has been minimized.

Show comment Hide comment
@JosephM3388

JosephM3388 Sep 18, 2017

Nice! This has been working nicely in my app. How would I go about removing all cookies from the cookie jar?
Would I call the "removeAllCookies" method?

Nice! This has been working nicely in my app. How would I go about removing all cookies from the cookie jar?
Would I call the "removeAllCookies" method?

@justinthomas-syncbak

This comment has been minimized.

Show comment Hide comment
@justinthomas-syncbak

justinthomas-syncbak Sep 22, 2017

@JosephM3388 You are correct, that should remove all the cookies correctly. There were some changes in API21 that favors the removeAllCookies(ValueCallback<Boolean> callback) method.

@JosephM3388 You are correct, that should remove all the cookies correctly. There were some changes in API21 that favors the removeAllCookies(ValueCallback<Boolean> callback) method.

@emezias

This comment has been minimized.

Show comment Hide comment
@emezias

emezias Oct 27, 2017

I am using this gist with the Persistent Cookie Store: https://github.com/franmontiel/PersistentCookieJar
So far, so good.

emezias commented Oct 27, 2017

I am using this gist with the Persistent Cookie Store: https://github.com/franmontiel/PersistentCookieJar
So far, so good.

@justinthomas-syncbak

This comment has been minimized.

Show comment Hide comment
@justinthomas-syncbak

justinthomas-syncbak Oct 30, 2017

@emezias This implementation uses the webkit's cookie jar, which is persistent by default. You shouldn't need to add an additional layer on top.

@emezias This implementation uses the webkit's cookie jar, which is persistent by default. You shouldn't need to add an additional layer on top.

@elye

This comment has been minimized.

Show comment Hide comment
@elye

elye Nov 5, 2017

Hi @justinthomas-syncbak, great code!
I have 2 questions
Does using your above code, we would no longer need to persist using https://github.com/franmontiel/PersistentCookieJar?
Also, what's the above code pros/cons when compare with the approach shared in from https://stackoverflow.com/a/18070681/3286489?

Thanks!

elye commented Nov 5, 2017

Hi @justinthomas-syncbak, great code!
I have 2 questions
Does using your above code, we would no longer need to persist using https://github.com/franmontiel/PersistentCookieJar?
Also, what's the above code pros/cons when compare with the approach shared in from https://stackoverflow.com/a/18070681/3286489?

Thanks!

@justinthomas-syncbak

This comment has been minimized.

Show comment Hide comment
@justinthomas-syncbak

justinthomas-syncbak Nov 6, 2017

@elye This code uses the same cookie jar that is used by the webviews. This provides you persistence by default because we are reading and writing to that cookie jar. The main aim for using the cookie manager this way is to shared session cookies between "disconnected" http calls to a service. For example, if you have a user login via a webview, it generally adds an auth cookie that needs to be shared when making API calls to your services. This will allow that cookie to be "seen" and passed through.

For the second question, they do the same thing in essence. Using the system provided HttpUrlConnection, which is now backed by OkHttp I believe, just means you need set the cookie jar in another way. By using the HttpUrlConnection you have to adhere to their conventions which is why you go about it differently.

If you use dependency injection in your project, you can provide the OkHttpClient that way. Another option is using a Singleton to provide it to your code. It is recommended to keep a single, shared, instance of an OkHttpClient around as it more resource efficient and can re-use connections on subsequent calls.

Owner

justinthomas-syncbak commented Nov 6, 2017

@elye This code uses the same cookie jar that is used by the webviews. This provides you persistence by default because we are reading and writing to that cookie jar. The main aim for using the cookie manager this way is to shared session cookies between "disconnected" http calls to a service. For example, if you have a user login via a webview, it generally adds an auth cookie that needs to be shared when making API calls to your services. This will allow that cookie to be "seen" and passed through.

For the second question, they do the same thing in essence. Using the system provided HttpUrlConnection, which is now backed by OkHttp I believe, just means you need set the cookie jar in another way. By using the HttpUrlConnection you have to adhere to their conventions which is why you go about it differently.

If you use dependency injection in your project, you can provide the OkHttpClient that way. Another option is using a Singleton to provide it to your code. It is recommended to keep a single, shared, instance of an OkHttpClient around as it more resource efficient and can re-use connections on subsequent calls.

@emezias

This comment has been minimized.

Show comment Hide comment
@emezias

emezias Jan 23, 2018

At last I have sorted it all out. The webview cookie manager is able to retain the value of the cookie. The expiration, the values for secure and httponly - these fields are not saved by the webview manager. This is where the PersistentCookieJar using serialization and shared preferences comes in to play. My implementation drops the code here in loadForRequest and returns super.loadForRequest instead. The WebviewCookieHandler extends PersistentCookieJar instead of CookieJar in order to get any "permanent" cookies to work properly. This took some time to work out so I am sharing the solution. The save from response logic loads the webview cookie manager but the network requests do not use that persistent storage - the shared preferences library does a better job. To restate, the gist class implements PersistentCookieJar instead of CookieJar, the loadForRequest function gets the list of cookies from shared preferences instead of the android.webkit.CookieManager and the saveFromResponse has a new call to super and calls saveAll on the SharedPrefsCookiePersistor. Methods to clear all cookies and clear session cookies also need adjustment.

@justinthomas-syncbak -
The issue that I'm hitting is that my cookies don't register as persistent. The expiresAt field (which populates persistent()) is misread or set wrong. The expires field looks good when I save, but persistent is not set to true on the cookie. When I load cookies for a request, the timestamp on the cookie string is correct but the timestamp is not. I'll get back to you. Neither this solution nor the shared preferences is working for (permanent) login token cookies as of now. The cookie is set and reset on the server side but okhttp3 is dropping it.

Unfortunately, my app seems to show that permanent cookies that should never be cleared (not session) are not being persisted by the Webview cookie manager.

emezias commented Jan 23, 2018

At last I have sorted it all out. The webview cookie manager is able to retain the value of the cookie. The expiration, the values for secure and httponly - these fields are not saved by the webview manager. This is where the PersistentCookieJar using serialization and shared preferences comes in to play. My implementation drops the code here in loadForRequest and returns super.loadForRequest instead. The WebviewCookieHandler extends PersistentCookieJar instead of CookieJar in order to get any "permanent" cookies to work properly. This took some time to work out so I am sharing the solution. The save from response logic loads the webview cookie manager but the network requests do not use that persistent storage - the shared preferences library does a better job. To restate, the gist class implements PersistentCookieJar instead of CookieJar, the loadForRequest function gets the list of cookies from shared preferences instead of the android.webkit.CookieManager and the saveFromResponse has a new call to super and calls saveAll on the SharedPrefsCookiePersistor. Methods to clear all cookies and clear session cookies also need adjustment.

@justinthomas-syncbak -
The issue that I'm hitting is that my cookies don't register as persistent. The expiresAt field (which populates persistent()) is misread or set wrong. The expires field looks good when I save, but persistent is not set to true on the cookie. When I load cookies for a request, the timestamp on the cookie string is correct but the timestamp is not. I'll get back to you. Neither this solution nor the shared preferences is working for (permanent) login token cookies as of now. The cookie is set and reset on the server side but okhttp3 is dropping it.

Unfortunately, my app seems to show that permanent cookies that should never be cleared (not session) are not being persisted by the Webview cookie manager.

@justinthomas-syncbak

This comment has been minimized.

Show comment Hide comment
@justinthomas-syncbak

justinthomas-syncbak Jan 29, 2018

@emezias If I understand correctly, when a cookie is read from the CookieManager and parsed using OkHttp Cookie.parse(String, String) method, the cookie's attributes don't seem to be reflected?

If I am right, it really isn't an issue unless you trying to manage the cookies yourself for some reason. The CookieManager is responsible for managing the cookies so it is the only one that really needs to know these what the value of these attributes are, and it does. When you ask it to load the cookies for a url, it will load the cookies that match the rules sent by the server using the Set-Cookie header. If the cookie has expired, the CookieManager won't return it to you. If the Secure attribute is set and you ask to load the cookies for a http address, the secure cookies won't be returned to you.

The only reason I could see a cookie getting "dropped" is because it has an expiration attribute set, and the CookieManager is clearing it because of this reason. The OkHttp also leaves it up the the CookieJar to implement the cookie rules, not the client directly.

Speaking of session cookies, you have highlighted the fact that this cookie jar, currently, doesn't honor session cookies and will treat them as persistent cookies.

@emezias If I understand correctly, when a cookie is read from the CookieManager and parsed using OkHttp Cookie.parse(String, String) method, the cookie's attributes don't seem to be reflected?

If I am right, it really isn't an issue unless you trying to manage the cookies yourself for some reason. The CookieManager is responsible for managing the cookies so it is the only one that really needs to know these what the value of these attributes are, and it does. When you ask it to load the cookies for a url, it will load the cookies that match the rules sent by the server using the Set-Cookie header. If the cookie has expired, the CookieManager won't return it to you. If the Secure attribute is set and you ask to load the cookies for a http address, the secure cookies won't be returned to you.

The only reason I could see a cookie getting "dropped" is because it has an expiration attribute set, and the CookieManager is clearing it because of this reason. The OkHttp also leaves it up the the CookieJar to implement the cookie rules, not the client directly.

Speaking of session cookies, you have highlighted the fact that this cookie jar, currently, doesn't honor session cookies and will treat them as persistent cookies.

@EiyuuZack

This comment has been minimized.

Show comment Hide comment
@EiyuuZack

EiyuuZack Mar 21, 2018

Greetings @justinthomas-syncbak,

When using your gist, the CookieManager method getCookie(String) keeps returning a cookie string like this:

Tue, 20 Mar 2018 15:36:36 GMT; foo=bar; bar=baz

According to the comment on your code the above method does not return any attributes, which I believe to be correct. That date is not an attribute but seems to be missing an Expires or Max-Age directive.

This breaks your gist because OkHttp Cookie.parse(String, String) will return null which will be added to the list, which will in turn be returned to the calling BridgeInterceptor, eventually causing a NullPointerException. The workaround I found was just do a null check before adding but that is just treating the symptoms.

Any ideas why Android's CookieManager might be returning such a string? I having trouble conceiving how such a broken cookie was saved into the WebView in the first place.

I'm targeting Android API 23 and using OkHttp version 3.4.1 if it helps.

EiyuuZack commented Mar 21, 2018

Greetings @justinthomas-syncbak,

When using your gist, the CookieManager method getCookie(String) keeps returning a cookie string like this:

Tue, 20 Mar 2018 15:36:36 GMT; foo=bar; bar=baz

According to the comment on your code the above method does not return any attributes, which I believe to be correct. That date is not an attribute but seems to be missing an Expires or Max-Age directive.

This breaks your gist because OkHttp Cookie.parse(String, String) will return null which will be added to the list, which will in turn be returned to the calling BridgeInterceptor, eventually causing a NullPointerException. The workaround I found was just do a null check before adding but that is just treating the symptoms.

Any ideas why Android's CookieManager might be returning such a string? I having trouble conceiving how such a broken cookie was saved into the WebView in the first place.

I'm targeting Android API 23 and using OkHttp version 3.4.1 if it helps.

@justinthomas-syncbak

This comment has been minimized.

Show comment Hide comment
@justinthomas-syncbak

justinthomas-syncbak Mar 27, 2018

@EiyuuZack That is interesting. Do you have visibility into the cookie's that are being passed down? Wondering if a value without a key is trying to be set.

@EiyuuZack That is interesting. Do you have visibility into the cookie's that are being passed down? Wondering if a value without a key is trying to be set.

@saipsa

This comment has been minimized.

Show comment Hide comment
@saipsa

saipsa Apr 9, 2018

@emezias Did you get to solve

working for (permanent) login token cookies as of now.

I am also blocked here

saipsa commented Apr 9, 2018

@emezias Did you get to solve

working for (permanent) login token cookies as of now.

I am also blocked here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment