How To Secure Your App’s Passwords with Safari AutoFill in iOS 8

Learn how to use Safari AutoFill in iOS 8 to generate passwords, securely save passwords, and share and synchronize passwords between your app and website. By Matt Luedke.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 2 of this article. Click here to view the first page.

Contents

Hide contents

How To Secure Your App’s Passwords with Safari AutoFill in iOS 8

25 mins

Optional: Autocomplete Values For Safari AutoFill

Try It Out

Where To Go From Here?

Note: At its core, the SSL signing command relies on trust. You need to provide keys that create a chain of trust linking your domain to the trusted Certificate Authority (CA) that issued your SSL certificate. Use your CA’s publicly available certificate (Ex: GeoTrust_Global_CA.pem) to signify the source of your certificate.

When ready, open up your Terminal and execute the following command (substituting your own domain name, of course):

This command ensures that this file has definitely come from you and isn’t corrupted by a “man in the middle” attack. Any Bundle IDs that the client sees listed in that JSON will be allowed to share passwords with your domain.

Note: It’s important that you use the exact filename apple-app-site-association as this is the file that iOS will be looking for.

Once you have the output, upload it directly to your domain’s root directory. For example, the associations file for my domain is located at:

https://mattluedke.com/apple-app-site-association

And that concludes the first half of the password-sharing scheme. Now onto the app itself.

With your Bundle IDs uploaded to your domain, the whole world can now see your Team ID and any apps that can share passwords. So how do you prevent someone from creating an app with one of those Bundle IDs and obtaining access?

Well, you’re going to use a provisioning process and code signature for your app to prevent such meddling.

You’ll certify who you are, via Apple, when you create your app, to prove the following:

Sign into Apple’s Developer Portal.

In Member Center, select Certificates, Identifiers & Profiles

certs_and_profiles

From that screen, click on Identifiers\App IDs. If you’ve already created an App ID for this app, select the correct App ID from the list and click the Edit button, or create a new one with the Add button.

If creating a new App ID, check the box for Associated Domains under App Services like this:

app_services

If you are editing an existing App ID, the interface looks like this:

editing_associated_domains

When you’re done, you’ll see Associated Domains activated for your App ID:

associated_domains_enabled

Then, click Provisioning Profiles, and create, download and install a new profile for that App ID (for a refresher on your way around Provisioning Profiles, check out the tutorial How to Submit Your App to Apple: From No Account to App Store, Part 1).

Once you’ve created, downloaded and installed a valid provisioning profile, you’ll tell Xcode to use the Associated Domain capability in your Swift project.

Open your Swift project in Xcode and navigate to your Target. Then click Capabilities and scroll down until you see the option for Associated Domains.

Turn the switch ON to activate the capability, and then click the + to add your domain to the list. The listing must be in the following format:

Once this is done, you’ll see the domain listed and the capability all ready to go, like this:

xcode_capabilities

It’s very important that your App ID, as specified in your Xcode project, matches perfectly with the App ID in the Member Center. Otherwise, you’ll see an error like this:

Screen Shot 2014-12-21 at 1.49.28 PM

You may also notice a new file has been created in your project with the .entitlements extension. Inside the file is the data you just entered, but in a slightly different form:

entitlements_file

This is the Entitlements File that iOS uses as a checklist against your code signing identity and provisioning profile.

And with that, you’re all ready to move on to coding the rest in Swift.

First, you’ll address the case where the user already has an account on your website. Imagine how thrilled they’ll be when the app magically knows their account, even when they open it for the very first time.

ragecomic

In Ultra Motivator, this is accomplished in SafariKeychainManager. It contains the following class method that you can use in your own project:

This method centers around SecRequestSharedWebCredential, which asks the device’s store of saved passwords for any relevant credentials it might be allowed to use. It has this signature:

As you can see, this method takes three parameters:

Here are the CFError possibilities you may receive in the completion block:

Note: These errors are described in the WWDC ’14 session Your App, Your Website, and Safari, but in practice they don’t always match up with original specifications. To be safe, check the entire domain association process when you receive an error, until Apple stabilizes the API.

But if you do receive some credentials, they will come in the form of either CFArrayRef or CFDictionaryRef objects. Unfortunately, the API won’t return credential objects, so you’ll have to extract the information you need with the CFDictionaryRef keys:

Note: The keys kSecAttrServer and kSecAttrAccount are both instances of CFStringRef, but kSecSharedPassword but is a CFTypeRef.

Now that you have this information, you can call your completion block and include the username and password, wrapping the call in dispatch_async(dispatch_get_main_queue()) {} so it’s guaranteed to be called on the main thread.

This way, the AutoFill-related code stays in SafariKeychainManager, and some other class, say a view controller, will handle what comes next.

For example, in Ultra Motivator checkSafariCredentialsWithCompletion is called in LoginViewController:

This block calls checkSafariCredentialsWithCompletion to check for credentials. If you receive something valid for username and password, you fill in the relevant text fields and proceed with a sign-in request — all without the user having to type a single thing.

Time to try it out! In the case where you’ve already navigated to the correct website in Safari, created an account and save the password, the credentials will show up on your device in Settings\Safari\Passwords & AutoFill\Saved Passwords, as shown here:

saved_password

Then, you’ll see a prompt to sign in with your credentials even if you’ve never opened the app before — provided you associated the app and website as outlined earlier in this article.

Note that SecRequestSharedWebCredential constructs the alert and handles the user input for you (and your user).

autofilled_signin

For many of your lucky future users, tapping OK is the only action they’ll ever need to take in-order to authenticate. Pretty amazing stuff!

Now that you’ve added the capability to read saved credentials, it’s time to complete the suite of CRUD operations. This will be especially valuable if a user starts with your app, but then later decides to use the website, or vice-versa.

In this case, you’ll offer AutoFill in reverse. That is, they’ll be able to sign right in, even if they’ve never visited before.

In Ultra Motivator, this is again accomplished in SafariKeychainManager:

Credential creating, updating and deleting all take place in SecAddSharedWebCredential:

A break down of the parameters:

Conveniently, SecAddSharedWebCredential supplies the necessary alert based on the context, and it also handles the user input. All you need to include in your view controller is a call to updateSafariCredentials, like so:

For example, when inserting a credential for the first time, as in SignupViewController, the operation simply executes without alerting the user.

When updating an existing credential, as in UpdateViewController, you’ll see the following confirmation alert:

update_password

And when removing an existing credential, as in HomeViewController, you’ll see the following confirmation:

delete_password

With your SafariKeychainManager complete, your users will have a fully synchronized and secure experience when authenticating with both your app and your website!

Instead of demanding that users perform keyboard acrobatics on what is such a small keyboard, you can generate a secure password for them with a single line by using: SecCreateSharedWebCredentialPassword.

The password generated will be 12 (uppercase or lowercase) letters and numbers, delimited with dashes, similar to this: 7nD-ft4-RCm-4M8.

Ultra Motivator offers to generate a password for the user when they create a new account in SignupViewController or update their password in UpdateViewController.

SecCreateSharedWebCredentialPassword returns an Unmanaged<CFString>!, and an example implementation would look something akin to this:

generate_password

If the user chooses OK from the dialog, then they get a shiny new password. It’s then placed in the relevant text fields and a sign-up request is fired-off.

Since you already arranged for AutoFill to remember this password, it’s not a problem that most humans would grapple with memorizing it. That’s right, there’ll be no hassle for you or your users!

Time to shift gears back to the web side of things. There are a few tiny, optional updates to your HTML that make it a little easier for Safari AutoFill to determine when to save your users’ passwords.

Use these three values for the autocomplete attribute to signal your intent to Safari:

You can see the autocomplete="username" attribute in action on all three pages of Ultra Motivator: the Signup, Signin and Update pages. It directs Safari AutoFill to save that input field’s info as the username.

Ultra Motivator uses the autocomplete="current-password" attribute on the Signin and Update pages. Safari AutoFill will now always attempt to fill this field.

Finally, the Signup and the Update pages use the autocomplete="new-password" attribute. Safari AutoFill should offer to save the contents of these fields. You can use the value more than once if you want the user to confirm their password.

Note: At the time of this writing, the new-password value in particular does not work as described in the WWDC ’14 session video.

Presumably, the Safari development team is still tweaking its functionality. In the meantime, the username and current-password values work, and you should implement the new-password value now so your page will be ready the moment Safari is.

You can test out the web interface right now on an iOS device or the simulator. Use these Ultra Motivator pages as examples:

Notice how Safari offers to save your password after you sign in. Who made that a reality? Oh, yeah, YOU did that.

safari_offer

Save a new set of credentials for your own pages, and then try to open your associated app and signing in for the first time. As if there was a wizard inside your computer conjuring up a password spell, the credentials are waiting for you on the app.

Now try going the other direction. Create a new account in your app, and then visit your website’s sign in page. AutoFill already knows your new credentials there too.

fill_password

If you used an auto-generated password, then the only typing you’ll have to do is to create a username. From there, you can generate a secure password and sign into your app and website in but a few taps.

It’s a good idea to test out both of these scenarios, as well as the password update scenario, and look for any edge cases. Things to look out for:

Note: When the user enables iCloud Keychain, the entire AutoFill process is even powerful as it enables password synchronization across all of you iCloud-enabled devices.

Feel free to use the sample code available in the Ultra Motivator repository.

If you’d like more information about sign-on services and password management on iOS, be sure to check out this WWDC 2014 session:

Also, the Ultra Motivator Swift project uses Alamofire, a popular open-source networking library. For more information on how to use it effectively, see our Beginning Alamofire Tutorial.

As always, but especially since this is a new feature and Apple’s Swift documentation for it is lacking, please make use of the comments below and our forums if you have any questions, comments or interesting new ways to use AutoFill.

Thank you for reading through this article. With your new skills, you can make using your brilliant apps easier to use and more secure than ever, and that’ll in turn lead to happier users, better reviews, and a uncanny feeling of technical awesomeness.

  1. That your app will be using the Associated Domains capability.
  2. That your Team ID is, in fact, valid.
  1. (Optional) fqdn: A fully qualified domain name for which you’d like to request credentials. If you pass .None here, the app simply checks all the domains listed in your entitlements file. Being explicit is not necessary here, but may help if you have different domains for development and production versions of an app.
  2. (Optional) account: If there is a specific account for which you’d like to request credentials, put it in here. You can also pass .None to make the app check for any valid credentials.
  3. completionHandler: A block that will deliver either a CFArray! or a CFError! after the request for credentials is done.
  1. The domains expected by AutoFill in both web and app contexts need to precisely match. Even a small difference can cause credentials to get lost or become out-of-sync across platforms.
  2. If you’re in the middle of developing your app and make changes to entitlements or AutoFill-related code, you may have to remove the app from your device and re-install.

Note: At its core, the SSL signing command relies on trust. You need to provide keys that create a chain of trust linking your domain to the trusted Certificate Authority (CA) that issued your SSL certificate. Use your CA’s publicly available certificate (Ex: GeoTrust_Global_CA.pem) to signify the source of your certificate.

Note: It’s important that you use the exact filename apple-app-site-association as this is the file that iOS will be looking for.

Note: These errors are described in the WWDC ’14 session Your App, Your Website, and Safari, but in practice they don’t always match up with original specifications. To be safe, check the entire domain association process when you receive an error, until Apple stabilizes the API.

Note: The keys kSecAttrServer and kSecAttrAccount are both instances of CFStringRef, but kSecSharedPassword but is a CFTypeRef.

Note: At the time of this writing, the new-password value in particular does not work as described in the WWDC ’14 session video.

Presumably, the Safari development team is still tweaking its functionality. In the meantime, the username and current-password values work, and you should implement the new-password value now so your page will be ready the moment Safari is.

Note: When the user enables iCloud Keychain, the entire AutoFill process is even powerful as it enables password synchronization across all of you iCloud-enabled devices.

cat json.txt | openssl smime -sign -inkey mattluedke.com.key
                              -signer mattluedke.com.pem
                              -certfile intermediate.pem
                              -noattr -nodetach
                              -outform DER > apple-app-site-association
webcredentials:mattluedke.com
class func checkSafariCredentialsWithCompletion(completion: ((username: String?, password: String?) 
    -> Void)) {
       
  let domain: CFString = "mattluedke.com"
     
  SecRequestSharedWebCredential(domain, .None, {
      (credentials: CFArray!, error: CFError?) -> Void in
           
    if let error = error {
      println("error: \(error)")
      completion(username: nil, password: nil)
    } else if CFArrayGetCount(credentials) > 0 {
      let unsafeCred = CFArrayGetValueAtIndex(credentials, 0)
      let credential: CFDictionaryRef = unsafeBitCast(unsafeCred, CFDictionaryRef.self)
      let dict: Dictionary<String, String> = credential as! Dictionary<String, String>
      let username = dict[kSecAttrAccount as String]
      let password = dict[kSecSharedPassword.takeRetainedValue() as! String]
      dispatch_async(dispatch_get_main_queue()) {
        completion(username: username, password: password)
      }
    } else {
      dispatch_async(dispatch_get_main_queue()) {
        completion(username: nil, password: nil)
      }
    }
  });
}
func SecRequestSharedWebCredential(fqdn: CFString!, account: CFString!, completionHandler: ((CFArray!, CFError!) -> Void)!)
SafariKeychainManager.checkSafariCredentialsWithCompletion({
  (username: String?, password: String?) -> Void in
  switch(username, password) {
  case let (.Some(username), .Some(password)):
    self.userNameField.text = username
    self.passwordField.text = password
    self.makeSignInRequest(false)
  default:
    break
  }
})
class func updateSafariCredentials(username: String, password: String) {
       
  let domain: CFString = "mattluedke.com"
       
  SecAddSharedWebCredential(domain,
      username as CFString,
      count(password) > 0 ? password as CFString : .None,
      {(error: CFError!) -> Void in
        println("error: \(error)")
      }
  );
}
func SecAddSharedWebCredential(fqdn: CFString!, account: CFString!, password: CFString!, 
    completionHandler: ((CFError!) -> Void)!)
SafariKeychainManager.updateSafariCredentials(username, password: password)
@IBAction private func promptForPassword(sender: AnyObject) {
  showPasswordGenerationDialog({
    let password = SecCreateSharedWebCredentialPassword().takeUnretainedValue()
    self.passwordField.text = password as String
    self.confirmPasswordField.text = password as String
    self.makeSignUpRequest()
  })
}
<input type="text" autocomplete="username">
<input type="password" autocomplete="current-password">
<input type="password" autocomplete="new-password">
Matt Luedke

Contributors

Matt Luedke

Author

Over 300 content creators. Join our team.