Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIWebView to view self signed websites (No private api, not NSURLConnection) - is it possible?

There's a load of questions which ask this: Can I get UIWebView to view a self signed HTTPS website?

And the answers always involve either:

  1. Use the private api call for NSURLRequest: allowsAnyHTTPSCertificateForHost
  2. Use NSURLConnection instead and the delegate canAuthenticateAgainstProtectionSpace etc

For me, these won't do.
(1) - means I can't submit to the app store successfully.
(2) - using NSURLConnection means the CSS, images and other things that have to be fetched from the server after receiving the initial HTML page do not load.

Does anyone know how to use UIWebView to view a self-signed https webpage please, which does not involve the two methods above?

Or - If using NSURLConnection can in fact be used to render a webpage complete with CSS, images and everything else - that would be great!

Cheers,
Stretch.

like image 218
Stretch Avatar asked Jul 20 '12 05:07

Stretch


People also ask

What does UIWebView mean?

Android is powered by Chrome. Mobile Safari UIWebView. The UIWebView is different from the ordinary Safari browser, as it is not a stand-alone browser, but merely browser functionality that is embedded in a third party app that allows the app to display content from the web.

What is UIWebView safari?

What is UIWebView? UIWebView is a deprecated iOS user interface control in Apple's UIKit framework. It loads HTML files and web content into an app view, rendering them as they would appear in a browser window. See developer.apple.com/documentation/uikit/uiwebview.

Does WKWebView use Safari?

WKWebView - This view allows developers to embed web content in your app. You can think of WKWebView as a stripped-down version of Safari. It is responsible to load a URL request and display the web content. WKWebView has the benefit of the Nitro JavaScript engine and offers more features.

Is Webview deprecated?

Deprecating Facebook Login support on Android WebViewsBeginning October 5, 2021, Facebook Login will no longer support using Android embedded browsers (WebViews) for logging in users.


2 Answers

Finally I got it!

What you can do is this:

Initiate your request using UIWebView as normal. Then - in webView:shouldStartLoadWithRequest - we reply NO, and instead start an NSURLConnection with the same request.

Using NSURLConnection, you can communicate with a self-signed server, as we have the ability to control the authentication through the extra delegate methods which are not available to a UIWebView. So using connection:didReceiveAuthenticationChallenge we can authenticate against the self signed server.

Then, in connection:didReceiveData, we cancel the NSURLConnection request, and start the same request again using UIWebView - which will work now, because we've already got through the server authentication :)

Here are the relevant code snippets below.

Note: Instance variables you will see are of the following type:
UIWebView *_web
NSURLConnection *_urlConnection
NSURLRequest *_request

(I use an instance var for _request as in my case it's a POST with lots of login details, but you could change to use the request passed in as arguments to the methods if you needed.)

#pragma mark - Webview delegate  // Note: This method is particularly important. As the server is using a self signed certificate, // we cannot use just UIWebView - as it doesn't allow for using self-certs. Instead, we stop the // request in this method below, create an NSURLConnection (which can allow self-certs via the delegate methods // which UIWebView does not have), authenticate using NSURLConnection, then use another UIWebView to complete // the loading and viewing of the page. See connection:didReceiveAuthenticationChallenge to see how this works. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; {     NSLog(@"Did start loading: %@ auth:%d", [[request URL] absoluteString], _authenticated);      if (!_authenticated) {         _authenticated = NO;          _urlConnection = [[NSURLConnection alloc] initWithRequest:_request delegate:self];          [_urlConnection start];          return NO;     }      return YES; }   #pragma mark - NURLConnection delegate  - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; {     NSLog(@"WebController Got auth challange via NSURLConnection");      if ([challenge previousFailureCount] == 0)     {         _authenticated = YES;          NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];          [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];      } else     {         [[challenge sender] cancelAuthenticationChallenge:challenge];     } }  - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; {     NSLog(@"WebController received response via NSURLConnection");      // remake a webview call now that authentication has passed ok.     _authenticated = YES;     [_web loadRequest:_request];      // Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!)     [_urlConnection cancel]; }  // We use this method is to accept an untrusted site which unfortunately we need to do, as our PVM servers are self signed. - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {     return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; } 

I hope this helps others with the same issue I was having!

like image 131
Stretch Avatar answered Oct 10 '22 21:10

Stretch


Stretch's answer appears to be a great workaround, but it uses deprecated APIs. So, I thought it might be worthy of an upgrade to the code.

For this code sample, I added the routines to the ViewController which contains my UIWebView. I made my UIViewController a UIWebViewDelegate and a NSURLConnectionDataDelegate. Then I added 2 data members: _Authenticated and _FailedRequest. With that, the code looks like this:

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {     BOOL result = _Authenticated;     if (!_Authenticated) {         _FailedRequest = request;         [[NSURLConnection alloc] initWithRequest:request delegate:self];     }     return result; }  -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {     if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {         NSURL* baseURL = [_FailedRequest URL];         if ([challenge.protectionSpace.host isEqualToString:baseURL.host]) {             NSLog(@"trusting connection to host %@", challenge.protectionSpace.host);             [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];         } else             NSLog(@"Not trusting connection to host %@", challenge.protectionSpace.host);     }     [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; }  -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)pResponse {     _Authenticated = YES;     [connection cancel];     [_WebView loadRequest:_FailedRequest]; } 

I set _Authenticated to NO when I load the view and don't reset it. This seems to allow the UIWebView to make multiple requests to the same site. I did not try switching sites and trying to come back. That may cause the need for resetting _Authenticated. Also, if you are switching sites, you should keep a dictionary (one entry for each host) for _Authenticated instead of a BOOL.

like image 40
Prof Von Lemongargle Avatar answered Oct 10 '22 21:10

Prof Von Lemongargle