7

I am logging in as a user via an OKHttpClient post and I would like to share the cookies with the webview.

Tim Nuwin
  • 2,775
  • 2
  • 29
  • 63
  • and your question is? if it is "how do I do that?", that's too broad. If it is "where do I find a tutorial to do that?", that will be closed too. Do you have a specific problem? – njzk2 Jan 07 '16 at 19:36
  • Despite the short description question here is clear, However is duplicated by this: http://stackoverflow.com/questions/7610790/add-custom-headers-to-webview-resource-requests-android – Than Jan 07 '16 at 19:37

3 Answers3

19

With OkHttp 3.0, you can use a method similar to sharing with HttpURLConnection by making a WebkitCookieManagerProxy that uses the webkit cookie store. Adapted from Pass cookies from HttpURLConnection (java.net.CookieManager) to WebView (android.webkit.CookieManager) .

public class WebkitCookieManagerProxy extends CookieManager implements CookieJar {
    private android.webkit.CookieManager webkitCookieManager;

    private static final String TAG = WebkitCookieManagerProxy.class.getSimpleName();

    public WebkitCookieManagerProxy() {
        this(null, null);
    }

    WebkitCookieManagerProxy(CookieStore store, CookiePolicy cookiePolicy) {
        super(null, cookiePolicy);
        this.webkitCookieManager = android.webkit.CookieManager.getInstance();
    }

    @Override
    public void put(URI uri, Map<String, List<String>> responseHeaders)
            throws IOException {
        // make sure our args are valid
        if ((uri == null) || (responseHeaders == null))
            return;

        // save our url once
        String url = uri.toString();

        // go over the headers
        for (String headerKey : responseHeaders.keySet()) {
            // ignore headers which aren't cookie related
            if ((headerKey == null)
                    || !(headerKey.equalsIgnoreCase("Set-Cookie2") || headerKey
                            .equalsIgnoreCase("Set-Cookie")))
                continue;

            // process each of the headers
            for (String headerValue : responseHeaders.get(headerKey)) {
                webkitCookieManager.setCookie(url, headerValue);
            }
        }
    }

    @Override
    public Map<String, List<String>> get(URI uri,
            Map<String, List<String>> requestHeaders) throws IOException {
        // make sure our args are valid
        if ((uri == null) || (requestHeaders == null))
            throw new IllegalArgumentException("Argument is null");

        // save our url once
        String url = uri.toString();

        // prepare our response
        Map<String, List<String>> res = new java.util.HashMap<String, List<String>>();

        // get the cookie
        String cookie = webkitCookieManager.getCookie(url);

        // return it
        if (cookie != null) {
            res.put("Cookie", Arrays.asList(cookie));
        }

        return res;
    }

    @Override
    public CookieStore getCookieStore() {
        // we don't want anyone to work with this cookie store directly
        throw new UnsupportedOperationException();
    }

    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        HashMap<String, List<String>> generatedResponseHeaders = new HashMap<>();
        ArrayList<String> cookiesList = new ArrayList<>();
        for(Cookie c: cookies) {
            // toString correctly generates a normal cookie string
            cookiesList.add(c.toString());
        }

        generatedResponseHeaders.put("Set-Cookie", cookiesList);
        try {
            put(url.uri(), generatedResponseHeaders);
        } catch (IOException e) {
            Log.e(TAG, "Error adding cookies through okhttp", e);
        }
    }

    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        ArrayList<Cookie> cookieArrayList = new ArrayList<>();
        try {
            Map<String, List<String>> cookieList = get(url.uri(), new HashMap<String, List<String>>());
            // Format here looks like: "Cookie":["cookie1=val1;cookie2=val2;"]
            for (List<String> ls : cookieList.values()) {
                for (String s: ls) {
                    String[] cookies = s.split(";");
                    for (String cookie : cookies) {
                        Cookie c = Cookie.parse(url, cookie);
                        cookieArrayList.add(c);
                    }
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "error making cookie!", e);
        }
        return cookieArrayList;
    }

}

Then add an instance of the proxy as your cookieJar when building the OkHttpClient.

client = new OkHttpClient.Builder().cookieJar(proxy).build();
snachmsm
  • 17,866
  • 3
  • 32
  • 74
RyanCheu
  • 3,522
  • 5
  • 38
  • 47
  • 1
    How to use the above together with PersistentCookieStore https://stackoverflow.com/a/34886860/3286489? – Elye Nov 05 '17 at 04:26
  • @Elye have you found a way to cooperate with `PersistentCookieStore`? – snachmsm Oct 03 '19 at 11:06
  • @snachmsm, you could check out https://medium.com/@elye.project/a-tale-on-android-cookies-store-management-b04832ca18c6 – Elye Oct 04 '19 at 09:45
  • thanks, I've found this post already, but still facing some issues in production environment... :( I'm or was using `PersistenCookieJar`, `PersistentCookieStore`, built-in `CookieSyncManager`, `CookieManager` and `CookieHandler`, and probably a few more methods (e.g. delaying giving time for sync), also mixing them - one frustrated user is saying that every app update is clearing his cookies (Mate 10 Pro,9.0), also I'm seeing in crashlytics that few users a week aren't obtaining cookie, but sysadmin is saying that there is no problem on server-side. I've never reproduced these two behaviors... – snachmsm Oct 04 '19 at 10:37
  • Keep in mind that half of this code is not required if you're not using `HttpURLConnection`. `OkHttp + WebView` only version: https://stackoverflow.com/a/58982263/858448 – Ghedeon Nov 21 '19 at 19:21
3

This link helped but I had to modify a few things for my use-case: http://artemzin.com/blog/use-okhttp-to-load-resources-for-webview/

The below code works:

webView = (WebView) findViewById(R.id.webView);    
WebSettings webSettings = webView.getSettings();

//enable javascript...
webSettings.setJavaScriptEnabled(true);

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        progress.dismiss();
    }

    @SuppressWarnings("deprecation")
    @Override
    public WebResourceResponse shouldInterceptRequest(@NonNull WebView view, @NonNull String url) {
        return handleRequestViaOkHttp(url);
    }
});

webView.loadUrl("MY_URL.COM");

Then the code that does the basic auth + handles intercepting the webview request using OkHTTPClient.

@NonNull
private WebResourceResponse handleRequestViaOkHttp(@NonNull String url) {
    try {
        OkHttpClient client = new OkHttpClient();

        client.setAuthenticator(new Authenticator() {

            //for basic authorization...
            @Override
            public Request authenticate(Proxy proxy, com.squareup.okhttp.Response response) throws IOException {
                String credential = Credentials.basic(CommonResource.HEADER_USERNAME, CommonResource.HEADER_PASSWORD);
                return response.request().newBuilder().header("Authorization", credential).build();
            }

            @Override
            public Request authenticateProxy(Proxy proxy, com.squareup.okhttp.Response response) throws IOException {
                return null;
            }
        });

        final Call call = client.newCall(new Request.Builder()
            .url(url)
            .build()
        );

        final Response response = call.execute();

        return new WebResourceResponse("text/html", "UTF-8", response.body().byteStream());
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}
Tim Nuwin
  • 2,775
  • 2
  • 29
  • 63
  • 1
    This is not a good idea, see https://artemzin.com/blog/android-webview-io/. Intercepting webview network requests makes them execute sequentially prior to chrome 47, and Android clients prior to 5.0 do not update their web views. – RyanCheu Aug 02 '16 at 23:01
2

If all you care about is OkHttp + WebView than it can be as simple as this:

class WebkitCookieManager (private val cookieManager: CookieManager) : CookieJar {

    override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
        cookies.forEach { cookie ->
            cookieManager.setCookie(url.toString(), cookie.toString())
        }
    }

    override fun loadForRequest(url: HttpUrl): List<Cookie> =
        when (val cookies = cookieManager.getCookie(url.toString())) {
            null -> emptyList()
            else -> cookies.split("; ").mapNotNull { Cookie.parse(url, it) }
        }
}

....


Usage:
 1. OkHttp
OkHttpClient.Builder().cookieJar(webkitCookieManager)

 2. WebView
CookieManager.getInstance().setCookie()

No need to support HttpURLConnection if it's not being used.

Ghedeon
  • 1,643
  • 1
  • 18
  • 30