42

I'm trying to load a HTTPS url with an self-signed certificate in a WKWebView for iOS 8 and it keeps failing. The workaround used with UIWebView (using setAllowsAnyHTTPSCertificate from NSUrlRequest) doesn't seem to work. Does anyone know of any workaround?

I do not need a solution that is valid for AppStore, as I only need to access self-signed certificate sites on development phases, not on production, but it's really a problem for development and testing server instances.

Thank you in advance.

Roi
  • 421
  • 1
  • 4
  • 5

8 Answers8

67

This is fixed in iOS 9! WKWebView finally makes calls to webView(_:didReceiveAuthenticationChallenge:completionHandler:) on WKNavigationDelegate. Unfortunately this does not work if you run code built in Xcode 7 on iOS 8 devices (at least not in my initial testing).

In my example below, I'm not actually doing anything with the cert and just letting it pass through without doing any further validation (obviously a bad plan for production code). See Apple's docs (Listing 3) for more details of what they want you to do here.

Swift:

func webView(webView: WKWebView, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge,
    completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
        let cred = NSURLCredential.init(forTrust: challenge.protectionSpace.serverTrust!)
        completionHandler(.UseCredential, cred)
}

Swift 3:

let cred = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(.useCredential, cred)

Swift 4:

func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    let cred = URLCredential(trust: challenge.protectionSpace.serverTrust!)
    completionHandler(.useCredential, cred)
}

Objective-C

NSURLCredential * credential = [[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
Lorenzo B
  • 33,216
  • 24
  • 116
  • 190
jvoll
  • 999
  • 10
  • 16
  • Can you please let me know how to use this code? I'm a complete novice in swift, I have overridden the `viewDidLoad` function and loading my url using: `webView.loadRequest(NSURLRequest(URL: url))` but how do I actually use your function? again sorry if the answer is obvious – gkpo Mar 31 '16 at 10:05
  • You'll need to give you WKWebView a class that implements `WKNavigationDelegate`. In your `WKNavigationDelegate` you can override the above method. In that method, you'll want something like I have above to allow the request to go through (assuming that's what you want to do). – jvoll Apr 04 '16 at 03:13
  • 1
    In my case the challenge.protectionSpace.serverTrust is resolved to nil. The app crash – Blazej SLEBODA Aug 28 '18 at 13:47
  • 1
    Hi @jvoll, great post. Would you improve your post on how this would look like for SwiftUI using `WKWebView` in Swift 5? – Egel Oct 01 '19 at 12:51
  • How to make this code conditional. I mean, I need to display an alert for the invalid ssl cert websites and if user say visit anyways then only load such website. Please let me how to do that? – Suryakant Sharma Aug 21 '20 at 11:59
  • This one works well on iOS 15 swift 5.x – Alessandro Ornano Apr 21 '22 at 07:10
14

I spent a lot of time looking into this, as an ios newbie, none of the solutions proposed were complete in my opinion. So here is what I did to get WKWebView to work in my case (very simple web view that needs access to self signed cert for dev only):

First thing: in my root Info.plist file, I added "App Transport Security Settings" as a dictionary, and add "Allow Arbitrary Loads" item with a value of YES.

App Transport Security Settings Allow Arbitrary Loads

Second: I added this code to my ViewController (inherits UIViewController and WKNavigationDelegate) - this was sourced from several answers elsewhere

func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    guard let serverTrust = challenge.protectionSpace.serverTrust else { return completionHandler(.useCredential, nil) }
    let exceptions = SecTrustCopyExceptions(serverTrust)
    SecTrustSetExceptions(serverTrust, exceptions)
    completionHandler(.useCredential, URLCredential(trust: serverTrust))
}

NOTE THAT THIS SOLUTION WILL LIKELY BE REJECTED BY THE APP STORE - I WILL SUBMIT TO THE APP STORE WITH "Allow Arbitrary Loads" ITEM WITH A VALUE OF NO.

simong5443
  • 171
  • 1
  • 4
  • Works for me. According to Apple, better call SecTrustEvaluateWithError(_:_:) after SecTrustSetExceptions(). https://developer.apple.com/documentation/security/1395676-sectrustsetexceptions – Bruce Li May 21 '20 at 09:33
  • @simomg5443 - I have exactly same requirement as in the question above. I need it for development only not for submission. Followed your answer turned on "Allow Arbitrary Loads" to YES and added - - (void)webView:(WKWebView*)webView didReceiveAuthenticationChallenge:(nonnull NSURLAuthenticationChallenge *)challenge completionHandler:(nonnull void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler Problem - It is not being called! Can you suggest? – user1664018 Mar 08 '23 at 18:21
8

I have the same error, and try to solve it using the Most Voted answer above, I used the following code to create a NSURLCredential object, but it failed.

NSURLCredential * credential = [[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust];

Then I found a solution in Apple Developer Forums. This helped me :

- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
  NSLog(@"Allow all");
  SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
  CFDataRef exceptions = SecTrustCopyExceptions (serverTrust);
  SecTrustSetExceptions (serverTrust, exceptions);
  CFRelease (exceptions);
  completionHandler (NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:serverTrust]);
  }
guozqzzu
  • 839
  • 10
  • 12
7

Try this:

 func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    if let serverTrust = challenge.protectionSpace.serverTrust {
        let credential = URLCredential(trust: serverTrust)
        completionHandler(.useCredential, credential)
    }else{
         completionHandler(.useCredential, nil)
    }

}
Faipdeoiad
  • 102
  • 2
  • 3
4

Looks like there may not be a solution at this stage (iOS 8.1.1). One might have expected the WKNavigationDelegate method webView:didReceiveAuthenticationChallenge:completionHandler: to handle this, but based on this Apple developer forum discussion an Apple employee confirms that this delegate method currently doesn't get called when self-signed certificates are encountered.

user2067021
  • 4,399
  • 37
  • 44
1

Open the URL in Safari on the device once, there you will be prompted with the option to accept the certificate. Once accepted it should work in your app as well, as the certificate is now known to the device. This is a per device workaround, it will in no way affect your app at release time.

dogsgod
  • 6,267
  • 6
  • 25
  • 53
  • That's strange -do you see the certificate in your device settings in general > profile section? – dogsgod Nov 25 '14 at 18:39
  • I do not see the prompt on device, although I can see the file in safari. And once I try to see from app it does not show. – user1664018 Apr 11 '23 at 15:32
0

iOS 10+ ignores NSAllowsArbitraryLoads: check here. And be cautious about other flags because Apple will require justification for AppStore review. And ignore solutions related to UIWebview, Apple will reject it anyway.

The simple way

  1. Download the self-signed Root CA on the device
  2. Trust it inside settings > about > ... certificate..

This is obviously inappropriate for large number of target audiences.

The better but harder way

  1. Read the guideline
  2. implementate WKNavigationDelegate webView(_:didReceive:completionHandler:)

More about websocket (OSError -9807)

There was an unfixed webkit bug for websocket (since 2015!), and it is different authentication routing so no way to override system default behavior for now. Apple forum threads:

clarkttfu
  • 577
  • 6
  • 11
0

Work around - Download/save the cert on device in 'files', install it, trust the certificate then go to Certificate Trust settings and enable full trust for root certificates. You should be good now. This is just for dev testing and nothing to do with production releases. Hope will help and save some time for others.