apache / cordova-plugin-inappbrowser

Apache Cordova InAppBrowser Plugin
https://cordova.apache.org/
Apache License 2.0
1.12k stars 2.15k forks source link

Issue with Cookie #753

Open ashishkumar2804 opened 4 years ago

ashishkumar2804 commented 4 years ago

Bug Report

In my app, i am using inapp browser to authenticate the user and then close the browser. used to then store cookie and call rest api to retrieve data. This used to work perfectly fine with UIWbview. With WkWebview the cookies are not persisting between In app browser and cordova wkWebview.

Problem

How do i retrieve cookie from InApp browser and store it in Cordova wkWebview instance?

What is expected to happen?

Cookie should persist between InAppBrowser and cordova WkWebview session.

What does actually happen?

Cookie is not available in Cordova WkWebview post authentication using In app browser window

Information

Command or Code

Environment, Platform, Device

In App browser plugin version 4.0.0, Cordova ios version 5.1.1 using Wkwebview only IOS 13.4

Version information

Checklist

ashishkumar2804 commented 4 years ago

anyone can help on this please..

NiklasMerz commented 4 years ago

Cookies persist between the main webview and inappbrowser, too. But you now might get problems because third party cookies are restricted. Please try searching the internet a bit. This should not be a Cordova issue.

ashishkumar2804 commented 4 years ago

it used to work fine with UIWebview. But with WKWebview, things are breaking.

ashishkumar2804 commented 4 years ago

how would you define a third party cookie? we are authenticating user on the same domain just it is being done in in-app-browser.

breautek commented 4 years ago

how would you define a third party cookie?

Third party cookies are cookies set from a different origin. Your cordova webview will either have an origin of null or if you're using schemes on cordova-ios, it will be set to your scheme (e.g: app://localhost).

it used to work fine with UIWebview. But with WKWebview, things are breaking.

WKWebView has added a lot restrictions surrounding cookies. More restrictions may come to iOS in iOS 14 via ITP.

Unfortunately this is something outside of Cordova's control. I'd consider moving away from cookies as a storage mechanism if possible. In my apps, I use HTTP headers to send authentication tokens, and I store them in a flat file using the cordova-plugin-file. Other storage methods is also suitable depending on your use cases.

ashishkumar2804 commented 4 years ago

Even if i move to authentication tokens being send in Http headers, how would i retrieve them, as the initial authentication happens in the in app browser. Do we have a way to read them?

breautek commented 4 years ago

In more detail... in my apps, I have an authentication API that returns a JSON response, which contains the user's auth token, which I request via XMLHttpRequest. I don't actually use in app browser, since that's generally used to display UI that is outside of the app.

Once I have the authentication token, it is saved to disk in a private location only readable by the application. On app launch, it is read to determine if A) it is valid, and B) if it is expired to determine if the login screen should be presented or if I can proceed to the main screen.

Throughout the lifetime the session, all API calls contains a custom header containing the auth token to the server. The server reads the request headers instead of cookies for authorising requests.

This obviously requires some work managing tokens on the frontend client.

Staying on the cookie issue though, there is a plugin that hack fixes some cookie-related problems, namely syncing, but I have no idea if they will work with cookies from in-app browser webview, or if they deal with third-party restrictions.

ashishkumar2804 commented 4 years ago

Regarding the cookies plugin you suggested, i did use this exact plugin, but it is not working with in app browser. I am still figuring out if i can do some changes in the delegates to get this working.

Regarding the token authentication, we do need to show login screen to the user at least once so that they can provide userName and password. My question was on response of that, how will i get the authorization token from the response header. May be i have to figure out how to present the login screen in the main view rather than in app browser.

niklas-wortmann commented 4 years ago

We are actually running into a similar problem. We are using microsoft saml for authentication. We can get successfully through the login process within an inAppBrowser, but afterward, the cookie is not persisted and won't be used for any further request. In our case, it is a third party cookie and therefore I am aware I can not access it programmatically but I would assume that it would persist the cookie and just sends it with every http call. We tried doing xhr calls but also native http calls using the ionic native http plugin.

vvsou862 commented 4 years ago

Hi, We are facing similar issue.In inappbrowser we are loading third party login page where they will send saml response with redirect to our url and we reading the saml response and creating cookie . after the cookie created we close the inappbrowser. cookie is not being passed to main webview and getting unauthorized exception.

drogerie21 commented 4 years ago

We have almost the same problem but have to handle cross-domain cookies.

With cordova-ios 5.1.1 and inappbrowser-plugin 3.2.0 in connection with the cookie-emperor plugin (https://github.com/RTK/cordova-cookie-emperor) everything works fine. But with the new inappbrowser-plugin in v4.0.0 we cannot get the cookies set by the "third-party" authentication system.

Of course, we can switch to another authentication method. In fact, our app can handle different methods to authenticate. But in this case, our customer is dependent on handling authentication this way.

batmanbury commented 4 years ago

Regarding the cookies plugin you suggested, i did use this exact plugin, but it is not working with in app browser. I am still figuring out if i can do some changes in the delegates to get this working.

@ashishkumar2804 Did you have any success with this? Maybe in a fork of https://github.com/CWBudde/cordova-plugin-wkwebview-inject-cookie ?

ashishkumar2804 commented 4 years ago

@batmanbury : I have added similar code written in the plugin you suggested. manually syncing cookie between wkhttpcookiestore and NSHttpCookie storage. things are working fine post that.

sandrolombardo commented 3 years ago

We have the same problem, but just in the other direction. We authenticate our users in the Cordova iOS App via WkWebview and get an authenticated cookie sent by the authentication system. Then we want to open a URL via the Cordova InAppBrowser plugin and have to include the authenticated cookie. Unfortunately the synchronization of cookies between WkWebview and Cordova InAppBrowser plugin does not work and our requests are sent without any cookies.

Does anybody already have a solution for this?

ashishkumar2804 commented 3 years ago

@sandrolombardo what do you mean by you authenticating user using WkWebview? are you doing authentication using API call from javascript?

sandrolombardo commented 3 years ago

@ashishkumar2804 We are doing authentication via jQuery Ajax-Calls (in this case requests are sent by WkWebview). Doing this we have the authenticated cookie stored in the WkWebview but not in the Cordova InAppBrowser Plugin. So, after a successful login each request sent by the WkWebview inlcudes the authenticated cookie. But, requests sent by Cordova InAppBrowser Plugin does not include the authenticated cookie since the cookie synchronization between Cordova InAppBrowser Plugin and WkWebview seems not to work.

ashishkumar2804 commented 3 years ago

@sandrolombardo : when you do authentication using Jquery-Ajax calls, cookie will be stored in NSHTTPCookieStorage namespace. you will need to manually save it in WKHTTPCookieStore this is the store, from where WKwebview(In-app browser) will take cookies and sent it along the request. You can use below sample code to do so.

NSString *domain = (NSString*) [cdvCommand.arguments objectAtIndex:0]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSHTTPCookie *cookie; NSArray *cookieArray = (NSArray*)[defaults objectForKey:[NSString stringWithFormat:@"cdvCookieJar:@%@", domain]]; for (NSDictionary *cookieProps in cookieArray) { cookie = [[NSHTTPCookie alloc] initWithProperties:cookieProps]; [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; } WKWebView* wkWebView = (WKWebView*) self.webView; WKHTTPCookieStore* cookieStore = wkWebView.configuration.websiteDataStore.httpCookieStore; NSArray *httpCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; for (NSHTTPCookie *cookie in httpCookies) { [cookieStore setCookie:cookie completionHandler:nil]; }

sandrolombardo commented 3 years ago

@ashishkumar2804 Thank you for your input! We're going to try this out.

rathodkaran07 commented 3 years ago

@sandrolombardo : when you do authentication using Jquery-Ajax calls, cookie will be stored in NSHTTPCookieStorage namespace. you will need to manually save it in WKHTTPCookieStore this is the store, from where WKwebview(In-app browser) will take cookies and sent it along the request. You can use below sample code to do so.

NSString *domain = (NSString*) [cdvCommand.arguments objectAtIndex:0]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSHTTPCookie *cookie; NSArray *cookieArray = (NSArray*)[defaults objectForKey:[NSString stringWithFormat:@"cdvCookieJar:@%@", domain]]; for (NSDictionary *cookieProps in cookieArray) { cookie = [[NSHTTPCookie alloc] initWithProperties:cookieProps]; [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; } WKWebView* wkWebView = (WKWebView*) self.webView; WKHTTPCookieStore* cookieStore = wkWebView.configuration.websiteDataStore.httpCookieStore; NSArray *httpCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; for (NSHTTPCookie *cookie in httpCookies) { [cookieStore setCookie:cookie completionHandler:nil]; }

Can you help with how and where to write this code. Stuck with this issue from a long time, any help would be really appreciated.

adamdport commented 3 years ago

@sandrolombardo it's been a few months, any luck? If this code works, seems like something that should be included as part of the plugin, or at least optionally enabled with a flag or something?

chupetoide commented 3 years ago

Same issue here. Tried with https://github.com/CWBudde/cordova-plugin-wkwebview-inject-cookie but nothing changed. Not sure where should I put the code wkWebView.injectCookie(Constants.serverUrl);. I put it inside onDeviceReady(). Any recommendations ?

grandhisubrahmanyam35 commented 3 years ago

I am also facing the same issue. Tried the above approaches but no luck. Can anyone help me with this?

emily-curry commented 3 years ago

I have successfully worked around this issue by doing some method-swizzling with some of this plugin's internals. What is happening here is that on browser open, we sync the app's persistent NSHTTPCookieStorage with the new webview's WKHTTPCookieStore. On browser close, we do the reverse; read from the WKHTTPCookieStore and write to the persistent NSHTTPCookieStorage (modifying any properties that would cause the cookies to not persist between app loads, because that is important to my use case).


#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "CDVWKInAppBrowser.h"

/**
 * A category of CDVWKInAppBrowser that handles persisting cookies from the WKWebView on close, and reapplying them on load.
 */
@interface CDVWKInAppBrowser (Cookies)
- (void) cookies_open:(NSURL*)url withOptions:(NSString*)options;
- (void) cookies_close;
@end

@implementation CDVWKInAppBrowser (Cookies)
/**
 * Swizzles the implementations of openInInAppBrowser:withOptions: and browserExit with versions that syncronize WKWebView cookies with the app's NSHTTPCookieStorage.
 */
+ (void) load
{
    static dispatch_once_t once_token;
    dispatch_once(&once_token,  ^{
        // ignore undeclared selector warnings, which are caused by overriding private methods of CDVWKInAppBrowser
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
        SEL openSelector = @selector(openInInAppBrowser:withOptions:);
        SEL cookiesOpenSelector = @selector(cookies_open:withOptions:);
        Method originalOpenMethod = class_getInstanceMethod(self, openSelector);
        Method extendedOpenMethod = class_getInstanceMethod(self, cookiesOpenSelector);
        method_exchangeImplementations(originalOpenMethod, extendedOpenMethod);

        SEL closeSelector = @selector(browserExit);
        SEL cookiesCloseSelector = @selector(cookies_close);
        Method originalCloseMethod = class_getInstanceMethod(self, closeSelector);
        Method extendedCloseMethod = class_getInstanceMethod(self, cookiesCloseSelector);
        method_exchangeImplementations(originalCloseMethod, extendedCloseMethod);
#pragma clang diagnostic pop
    });
}

/**
 * Loads all cookies from the app's NSHTTPCookieStorage instance, and applies them to the WKHTTPCookieStore.
 * The overridden method is called after this happens, so if the webview was opened with the "clearsession" option, cookies will still be cleared.
 */
- (void) cookies_open:(NSURL*)url withOptions:(NSString*)options
{
    WKWebsiteDataStore* dataStore = [WKWebsiteDataStore defaultDataStore];
    WKHTTPCookieStore* cookieStore = dataStore.httpCookieStore;
    NSHTTPCookieStorage* storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    NSHTTPCookie* cookie;
    for (cookie in storage.cookies) {
        [cookieStore setCookie:cookie completionHandler:nil];
    }
    [self cookies_open:url withOptions:options];
}

/**
 * Loads all cookies from the WKHTTPCookieStore, sets the "Discard" flag to false, and persists them to the app's NSHTTPCookieStorage instance.
 */
- (void) cookies_close
{
    WKWebsiteDataStore* dataStore = [WKWebsiteDataStore defaultDataStore];
    WKHTTPCookieStore* cookieStore = dataStore.httpCookieStore;
    int secondsToKeep = 60*60*24*90;
    [cookieStore getAllCookies:^(NSArray* cookies) {
        NSHTTPCookie* cookie;
        for(cookie in cookies) {
            NSMutableDictionary* cookieDict = [cookie.properties mutableCopy];
            [cookieDict removeObjectForKey:NSHTTPCookieDiscard]; // Remove the discard flag. If it is set (even to false), the expires date will NOT be kept.
            if (![cookieDict objectForKey:NSHTTPCookieExpires]) { // If the cookie doesn't have an expiration date, set it to the maximum value. Otherwise, keep the existing value.
                [cookieDict setObject:[NSDate dateWithTimeIntervalSinceNow:secondsToKeep] forKey:NSHTTPCookieExpires]; // Expires in 90 days. Only applies to version 0 cookies (rare).
            }
            if (![cookieDict objectForKey:NSHTTPCookieMaximumAge]) { // If the cookie doesn't have a max age, set it to the maximum value. Otherwise, keep the existing value.
                [cookieDict setObject:[NSString stringWithFormat:@"%d",secondsToKeep] forKey:NSHTTPCookieMaximumAge]; // Expires in 90 days. Only applies to version 1 cookies.
            }
            NSHTTPCookie* newCookie = [NSHTTPCookie cookieWithProperties:cookieDict];
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:newCookie];

        }
    }];
    [self cookies_close];
}

@end
NiklasMerz commented 3 years ago

Thank you @emily-curry for sharing your solution. You could make a pull request to integrate this change directly into the inappbrowser plugin.

I am not really sure if we would want to merge this change into the plugin, but with a PR we can discuss this better. Additionally I am not sure if this solves the issue as well.

I used your code to improve our Webview Proxy Plugin. This plugin is helping us and others to work around cookie, CORS and ITP issues. With you code I could create a fix for a similar bug where cookies from the WebView cannot be used by the proxy. A huge thank you for helping us find a solution with this comment and providing code to build it.

stefano-8wave commented 2 years ago

Hi! to fix wkwebview cookie problem have i to install this proxy plugin?