Keychain Reaction

In my previous post, I talked about a sample app I made that demonstrates Keychain entry persistence across app relaunches and app reinstalls.

What I didn’t talk about was what a pain in the ass working with the Keychain is.

Over the years, I’ve seen a lot of codebases that included a lot of utility classes to make dealing with those ancient, ancient C-based Keychain APIs a little easier.

What I haven’t been able to find is a modern Swift library that does so. For Secrets, I wound up just copying in the files from Sam Soffes’s SAMKeychain library, which from my cursory googling seemed like one of the most recently-updated Keychain helper libraries. But it’s in Objective-C.

Any Swift-y Keychain libraries out there?

Secrets and Lies

Turns out, my entire previous post was trying to solve a problem that doesn’t exist.

I assumed, because I’d heard rumors about it and found this authoritative-sounding forum post, that Apple had indeed removed the persistence of Keychain entries for an app if a user deleted the app from their iOS device.

But while Apple did this in a beta release, they didn’t ship it in the final version (thanks to Nick Lockwood for pointing this out to me).

I verified this for myself with the new sample project Secrets, and you can too by downloading and running it for yourself, on both iOS 10 and iOS 11.

The app just shows a simple view with one text field, where you can type in anything you’d like. It is then saved in the app’s Keychain.

If you kill the app and come back to it, that value is again displayed.

If you delete the app and reinstall it, the value is still displayed. This is true both in the iOS 10.3.1 Simulator and the iOS 11 Simulator in Xcode 9 beta 4.

Now, in the iOS 11 Simulator, due to a bug, you can’t delete an app through the regular Simulator user interface.

So instead, you must delete it by hand from the file system, by going to ~/Developer/Library/CoreSimulator/Devices/, finding the UUID that matches that of the Simulator you’re using, then within that finding the UUID of your app, inside data/Containers/Bundle/Application.

But if you go through all that trouble in iOS 11 Simulator, then restart the Simulator and verify that the app is gone, then reinstall it…the Keychain entry is still there.

So you don’t need to use Shared Web Credentials the way I described in my last blog post. You can continue to rely on local app Keychain entries to keep your users logged in, even if the user deletes your app and reinstalls it.

For now.

Password Autofill and Shared Web Credentials

Update: See my next post for why the problem described here isn’t actually a problem.

In Apple’s WWDC 2017 session “Introducing Password Autofill for Apps”, the presenter describes this scenario: a user downloads an app, but is put off from using it when they’re required to log in with a username and password first.

I’ve worked at a number of companies built around a consumer service, and they all worry about this.

If a user who doesn’t use something like 1Password sets up a strong password, it’s likely they’ll forget it by the next time they need to log in. You can have them reset it, but…will they bother?

Apple’s solution assumes the user has logged into the service in Safari, either on that device or, if they have iCloud Keychain turned on, on another Apple device.

When they go to the username or password text fields in your app on iOS 11, in the QuickType bar above the on-screen keyboard, Password Autofill shows a little key icon to the right, and some text telling them that it now contains the username and password for that service. If they tap it, it will fill in the text fields with the saved credentials, and they’re on their way.

Password Autofill will attempt, via heuristics, to provide this information even if you’ve done nothing to support it in your app (besides connecting your app to your website, see below), though there are ways you can tweak your app to make the user interface connection more explicit.

If the user hasn’t logged into the service in Safari previously, or they did it on another device and they don’t have iCloud Keychain turned on, they’re screwed. Apple doesn’t have that information, and can’t offer it to them.

What if they’ve logged into the service before in that app, then deleted the app, then reinstalled it?

In the past, you could rely on the local app-specific Keychain entries surviving under such circumstances. So your app could still access the username and password from there, and automatically log the user in. But Apple took this away in iOS 10.3.

My understanding here is that you can restore that functionality by using something closely related to Password Autofill, but older: Shared Web Credentials (third-party tutorials here and here).

Both technologies require an Apple-recognized association between your website and your application; the WWDC session for Password Autofill and the documentation for Shared Web Credentials both spend a lot of time describing how to set up that relationship, which is also needed for Universal Links.

By using the Shared Web Credentials APIs, you can tap into that same reservoir of usernames and passwords that Password Autofill uses, but completely bypass the username and password fields of your application: you get the data yourself and can use it directly.

Why does anyone need Password Autofill, then? My hunch is that Shared Web Credentials didn’t take off the way Apple wanted — it does require more work, after all. So they made something even easier, something app writers might not need to do anything at all to support.

Now let’s think about it in the other direction: if Password Autofill is so easy, why worry about Shared Web Credentials at all?

Because Shared Web Credential let you go in the other direction.

If a user does log in via your app, you can use Shared Web Credential to push that information into Safari (and iCloud Keychain, if it’s on). Then, if they delete the app and restore it, you can still log them in automatically by getting that information back out of Safari. Note: doing so isn’t invisible: the user will be prompted with an Apple-provided confirmation alert in both cases.

So if you really want to make things as easy as possible for your users, it seems to me you’ll want to support Shared Web Credentials in both directions, before worrying about Password Autofill at all. It’s not as foolproof as the old local Keychain solution, since the user can reject sharing their credentials with Safari, but it’s better than nothing.

Am I missing anything about this?