Update August 08, 2020
With the advent of iOS 14.0 there is a better way to do this now, check out App Attest.
Why
I’ve been sporadically building a mobile app as a side project. For years. Yes, years. I’ve been building it for a few reasons. One of them is that it’s a way to escape and relax. Another is that it’s a way to stay curious and keep learning. The rest of the reasons and the mobile app however is a post for another time. Let’s get to this post.
As part of building the mobile app I needed to retrieve weather data for the users current location. A design decision I made is that the mobile app is fully native to the iOS platform and Apple ecosystem. Therefore one constraint is that I didn’t want to introduce a separate identity system that would have impacted the user experience, and also meant I had to deal with the corresponding security concerns. I wanted a way to retrieve the weather data in the most trusted way possible - to respect the user, their data (current location), and hence their privacy.
Solution
Here’s the solution that I designed and built with full credit to Graeme Foster for giving me the concept for the critical part - how to secure the app to the API.
The solution is as follows:
-
Part 1: HTTPS and MSTA: When calling the API from the app implement SSL/TLS (no brainer), but more importantly MSTA (Manual Server Trust Authentication), i.e. SSL Pinning, to prevent MITM (Man In The Middle) attacks. Don’t you love acronyms!
-
Part 2: Secure App to API: When calling the API from the app implement a trusted connection - this involves the app ‘registering’ with the API and being given a SecretKey via a trusted secure channel, that it uses on subsequent API calls. See APNs (Apple Push Notification service) which I use for this, and explain in more detail below.
APNs delivery of remote notifications comes with some pretty heavy disclaimers in the Apple documentation linked to above, but I still decided to use it to implement the second part of the connection trust because:
-
The pattern only relies on using the remote notification occasionally, i.e. when the device token changes.
-
The app feature is optional - it reduces the user experience slightly, but it doesn’t really matter if it doesn’t work. For example, the user could decide to not give the app notification permission.
-
It’s an Apple built and operated method to deliver data to an app on a device via a trusted secure channel.
The following two sections go into detail on how to implement the two parts of the solution.
Note that only simplified non-production code snippets are shown for brevity. This is not industrialised and tested code.
MSTA (Manual Server Trust Authentication)
MSTA is pretty simple when calling APIs using the standard Foundation URLSession
request. It may be easier with a third party library, but I prefer to minimise use of dependencies.
See the Apple documentation Performing Manual Server Trust Authentication which clearly documents when and how you can perform MSTA for this scenario.
You want to reject credentials that would otherwise be accepted by the system. For example, you want to “pin” your app to a set of specific keys or certificates under your control, rather than accept any valid credential.
Here is a Swift code snippet that performs the MSTA. It has the API host hard-coded to 192.168.0.7, i.e. the API is local.
And here is an explainer of what’s happening in the code snippet:
-
The challenge type is server trust, and not some other kind of challenge. The challenge’s host name matches the host that you want to perform manual credential evaluation for.
-
Attempt to get the serverTrust property from the protection space. Fall back to default handling if the property is nil.
-
Pass the server trust to a private helper method checkValidity(of:) that compares the certificate in the server trust to known-good values stored in the app bundle.
I also found the following tutorial Preventing Man-in-the-Middle Attacks in iOS with SSL Pinning useful to understand how to retrieve the certificate from the API server and add it to the Xcode project.
The following terminal snippet copies the certificate output to openssl x509, specifies DER encoding and outputs it to a new file named localhost.der
.
Secure App to API
After I implemented MSTA, it was time to build the second part of the solution. Here’s a sequence diagram showing detail of the interactions of the components.
The following sections show code snippets for how I implemented the main steps in the register process, and then finally authenticating the device/app on subsequent API calls.
Register - App: Request
The first step is to register the device with the API. You do this from the didRegisterForRemoteNotificationsWithDeviceToken
method in AppDelegate.swift
.
Register - API: Create Secret Key for Token, Send Push Notification
The next step in the register process is to create the SecretKey from the Token, and send the push notification. I chose to build the API as an AWS Lambda ASP.NET Core Web API project, and use AWS SNS (Simple Notification Service) to send the push notification, so here’s a code snippet from the RegisterController.cs
class.
Here’s how I hashed the token with a salt.
Register - App: Receive Push Notification
The last step in the register process is to accept the SecretKey from APNs in the app, and securely store both the Token and SecretKey in the Keychain for use on subsequent API calls. Here’s a code snippet from the didReceiveRemoteNotification
method in AppDelegate.swift
.
Subsequent API Calls
Once the Register Process has been done, this is how I authenticate the device/app on subsequent API calls.
Here’s a code snippet from the WeatherController.cs
class.
Here’s how Validate
works in combination with the has Create
method above.
A Final Note
This article is concerned with respecting the user, their data, and hence their privacy. Processing and storing user data securely is not easy.
If you are building a mobile app to provide an experience, start from the user’s perspective and strongly assess what user data you need to process and store, and how you should do it.
If you need to store data for a backup, sync across devices or sharing to other users, Apple (and others) provide increasingly capable Backend as a Service (BaaS) offerings.