Shared Web Credentials

Episode #231 | 15 minutes | published on August 11, 2016 | Uses Xcode-7, iOS-9
Subscribers Only
In this episode we implement shared web credentials with a server, allowing users to automatically enter credentials in our app if they've already done so in Safari. We cover building a Sinatra app for our server, deployment to Heroku for free SSL and a unique domain, and adding the appropriate entitlements to our app.

Episode Links

Overview

There are 4 steps, according to the documentation:

  1. Add a com.apple.developer.associated-domains entitlement to your app. This entitlement must include all the domains with which you want to share credentials.

  2. Add an apple-app-site-association file to your website. This file must include application identifiers for all the apps with which the site wants to share credentials.

  3. When the app is installed, the system downloads and verifies the site association file for each of its associated domains. If the verification is successful, the app is associated with the domain.

4 An app can share credentials with any associated domains by calling SecAddSharedWebCredential and SecRequestSharedWebCredential.

Getting the server ready

For this example, we’ll use a dummy Sinatra web site that allows you to log in with any username, as long as the password is “password”.

We’ll deploy this to Heroku so that we can get it online and get SSL for free. HTTPS is a requirement to serve up the apple-app-site-association file.

To start, clone the server repository:

git clone https://github.com/subdigital/nsscreencast-shared-credentials-demo-server.git

Next, we’ll need to deploy this to Heroku. If you don’t already have an account, follow this Getting Started Guide.

Create the Heroku app by typing the following:

heroku create

This will take a few seconds and create the app for you. It will also spit out the git remote URL and the web URL for your new application.

To deploy to heroku, you need to push the code to the new heroku remote repository:

git push heroku master

When it’s finished, open the URL in your browser to see the application running.

You’ll also need to set a custom SESSION_SECRET environment variable which controls how the session cookie is encrypted. To do this, generate a unique string and use the following command:

heroku config:set SESSION_SECRET <YOURSECRETKEY>

Save a Password in the app

In the provided sample app, go to Settings -> Safari -> Passwords and enter in the domain, username, and password that you want saved.

If you’re running on device, you can tap the button in the app to open the site in Safari and log in there. It should prompt you to save the password, which will accomplish the same result.

Add the Entitlement

Next you need to enable the Associated Domains option in the Capabilities tab of the project settings.

Enter the domain that you used above, prefixed with webcredentials:

This should add the appropriate entitlements file.

Configure your server to share data with the app

Next we need to add the apple-app-site-association file to the server. Since this file needs to be delivered with a content-type of application/json, the easiest way is just to make this a custom endpoint in Sinatra. Alternatively you could configure your web server to return the appropriate content-type for this file.

Also note that in iOS 8 this file is required to be signed with a trusted certificate. In iOS 9 this is not necessary.

get '/apple-app-site-association' do
  content_type 'application/json'
  {
    "webcredentials": {
      "apps": [
        YOURTEAMID.com.yourcompany.yourapp
      ]
    }
  }.to_json
end

Make sure you have the fully qualified bundle identifier present including your Team ID.

Request Shared Web Credentials

In the app, in viewDidAppear, we request shared web credentials for all associated domains:

func checkSharedWebCredentials() {
        SecRequestSharedWebCredential(nil, nil) { (results, error) in
            if let e = error {
                print("error: \(e)")
            } else {

                if CFArrayGetCount(results) > 0 {
                    let ptr = CFArrayGetValueAtIndex(results, 0)
                    let dict = unsafeBitCast(ptr, CFDictionary.self) as Dictionary

                    let server = dict[kSecAttrServer as String] as! String
                    let user = dict[kSecAttrAccount as String] as! String
                    let password = dict[kSecSharedPassword as String] as! String

                    print("Found result for \(server)")

                    dispatch_async(dispatch_get_main_queue()) {
                        self.emailTextField.text = user
                        self.passwordTextField.text = password
                    }
                }

            }
        }
    }

And that's it!

blog comments powered by Disqus