Ben Dodson

Freelance iOS, macOS, Apple Watch, and Apple TV Developer

Creating passwordless user accounts within iOS apps

Want to keep up to date? Sign up to my free newsletter which will give you exclusive updates on all of my projects along with early access to future apps.

I’m currently in the process of writing an app which will store some information for each user on a server; the stored data should be available to the user on all of their devices. Usually there are two ways of dealing with this:

  1. Build a user access system such that the user has to register to use the service. They’ll need to log in to the app (usually with email address and password) on each of their devices.

  2. Use CloudKit to store all of the information in an Apple provided database.

The first option is overkill for this fairly simple app1 and the second is too restrictive as it means I need to use Apple’s specific data modelling system. However, there is a third way which uses a bit of each of them…

When a user is logged in to an Apple account on their device2, they are automatically signed into the iCloud system. With CloudKit, every app gets its own identifier based on both the CloudKit container and user account which doesn’t change. By retrieving this identifier, you can be 100% sure of the user that is using your app without knowing anything personal about them and without requiring them to manually log into your app.

To get the identifier, you first need to enable CloudKit within the iCloud section of the Capabilities panel in Xcode (which will require updating your provisioning profile). Then, it is a simple case of importing the CloudKit framework and using the following code:

let container = CKContainer.defaultContainer()
container.fetchUserRecordIDWithCompletionHandler { (recordID, error) in
    guard let recordID = recordID else {
        NSLog("Error: \(error")
        return
    }
    
    NSLog("Identifier: \(recordID.recordName)")    
}

The identifier returned will be 34 characters long and look something like this:

_e990774f93dd6625b11af6d40fceb310

Once you have that, you can then send it to your server to match it against whatever content you want to link to this particular user. Now whenever they use your app on any device with the same Apple account, they’ll have access to everything without the need for a manual account creation process and without having to hand over any personal information. The entire process is completely silent and is very secure as the identifier is generated from both the Apple account and your CloudKit container - it can’t be used by other apps nor can it be reverse-engineered to give you personal details of the user.

I find the code above a bit messy to be used multiple times throughout an application (not to mention the import CloudKit requirement) and so I’ve wrapped this up in a very basic Swift class that can be called as such:

AppleCloudIdentifier.fetch { (identifier, error) in
    guard let identifier = identifier else {
        NSLog("Error: \(error)")
        return
    }
    
    NSLog("Cloud identifier: \(identifier)")
}

I’ve named it AppleCloudIdentifier and the code is available on GitHub.

UPDATE [3rd May 2019]: I needed use of this in a recent project and so I’ve updated the code to make use of Swift 5 and the new Result type.

  1. It’ll also lead to a sharp drop-off in users as people don’t like having to create multiple accounts all over the place. You could implement something like Facebook Login but then you still have an issue in that you are requiring a fair amount of personal information upfront which you probably don’t need. ↩︎

  2. I’ll hazard a guess that 99.9% of iOS devices are logged into an Apple account. They need to be in order to download apps, send iMessages, create backups, etc, so very unlikely you wouldn’t be. ↩︎

Talking Shop with Ben Dodson » « The Divide #14 - WWDC 2016, 'iTunes with tabs'

Want to keep up to date? Sign up to my free newsletter which will give you exclusive updates on all of my projects along with early access to future apps.