Sharing Web Data with iOS Using WKWebView

I recently helped develop a native iOS app for a client that sells software to many different educational organizations. We wrote the app in Swift, and it interacts with our client’s pre-existing web API.

One challenge we faced was that many of our client’s customers require single-account, multiple-login (SAML) support through their own web portals. To support SAML, we needed an easy way to pass a user’s API credentials from a web page to our iOS application. In this post, I’ll show how this can be accomplished using WKWebView.

The Problem

The high-level overview of the issue we faced:

  • Our client provides software to many organizations, and each has users that must log in.
  • To log in, a user must identify his or her organization.
  • After identifying the organization, the user can log in using our client’s API or, in some cases, an external web site hosted by the organization.

With SAML, users are sent to the organization’s web page to log in, then redirected back to a landing page that our client controlls. Once that redirect occurs, we have the login credentials (for instance, a user token required by our client’s API). The question is: How do we pass that token to the native iOS app?

WKWebView

In iOS 8, Apple provided WKWebView as part of WebKit, a newer way of embedding web views in a native app. (Among other features, WKWebView boasts better performance than the older UIWebView.) In the following code samples, I will show how a developer can pass data in the form of a JavaScript object from a web page to the native iOS app.

First, create a simple UIViewController in Interface Builder and give it a custom class. I chose to name mine SamlLoginViewController. In the view controller’s loadView method, we create the WKWebView and set up the communication channel between the web and our app as follows:


import UIKit
import WebKit

class SamlLoginViewController: WKScriptMessageHandler {
    var webView: WKWebView?
    let userContentController = WKUserContentController()

    override func loadView() {
        super.loadView()

        let config = WKWebViewConfiguration()
        config.userContentController = userContentController

        self.webView = WKWebView(frame: self.view.bounds, configuration: config)
        userContentController.addScriptMessageHandler(self, name: "userLogin")

        self.view = self.webView
    }

Before diving into loadView, note that there are two instance variables declared at the top: webView and userContentController. Further, we are going to add the WKScriptMessageHandler protocol to our class. Our class will handle messages passed from JavaScript on the web page.

To create a WKWebView, we first construct a WKWebViewConfiguration object and set its userContentController value to the WKUserContentController on our class. The WKWebView can then be constructed like this:


let config = WKWebViewConfiguration()
config.userContentController = userContentController

self.webView = WKWebView(frame: self.view.bounds, configuration: config)

After creating the WKWebView, we set up the message handler:


userContentController.addScriptMessageHandler(self, name: "userLogin")

The side-effect of addScriptMessageHandler is that it will create an object that will be added to window.webkit.messageHandlers for any webpage loaded in the WKWebView. In this case, we name the handler “userLogin” so our JavaScript code will be able to access window.webkit.messageHandlers.userLogin.

Next, we need to create the delegate method to receive messages in our view controller:


func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
    let dict = message.body as! [String:AnyObject]
    let username    = dict["username"] as! String
    let secretToken = dict["secretToken" as! String 
    
    // now use the name and token as you see fit!
}

The JavaScript Object

The last piece of the puzzle is the JavaScript needed to send the message. The following code must be run on the page loaded into the WKWebView:



This code creates a simple HTML button and adds an even listener to call the messageHandler that is automatically set up by WKWebView. You can verify that window.webkit.messageHandlers.userLogin only exists in the context of your iOS app’s view. Just visit the URL in a browser and it will be undefined. When postMessage is called, the iOS app receives the sent JavaScript object. It’s that easy!

Conversation
  • Todd says:

    Funny, I’ve never seen that acronym for SAML before. I thought it was Security Assertion Markup Language.

  • igoMobile says:

    Had you a requirement in your project to return a value from native function back to JS calling function? Or maybe you know, if it’s possible to return a value or at least give js callback function as a parameter to the native function?

  • Alexandre Camilleri says:

    I have a question regarding the “sent javascript object” that we receive in the native code.

    What kind of object is it then? Javascript?
    How to handle it?

    Regards

    • John Fisher John Fisher says:

      Alexandre,

      It comes back as a dictionary:

      let dict = message.body as! [String:AnyObject]

      And you can use it as follows:

      let username = dict[“username”] as! String
      let secretToken = dict[“secretToken” as! String

      See the code snippet right above “The JavaScript Object”

      • Alexandre Camilleri says:

        Oh sorry I missed that… Thanks a lot John. Could have tested it but I didn’t had access to xCode at the time.

        Cheers

  • Emily Koagedal says:

    Hi I was wondering if there is a way to save the username and password that the user enters when the wkwebview is loaded. In my app, we don’t have a separate webview for login, it is just the same url that connects to the website. I need to access the username and password that the user enters in order to send it to the api and receive the user token. Any ideas?

    • John Fisher John Fisher says:

      You can do that using the same approach I outlined in this blog post. It’s probably not advisable to store the password– you may instead want to store the API token. Be sure to store it in a secure location. For iOS I recommend you use the Keychain.

      This cocoapod looks like a good bet:
      https://cocoapods.org/pods/SwiftKeychainWrapper

  • Karthik R says:

    Is there any way to interpret wbkit.postmessagehandler in UIWebView

  • Avinash Lingaloo says:

    I’m trying to do more or less the same thing but I cannot get the events back to ios.

    I have google this around but don’t seem to get my head around this issue.

  • Comments are closed.