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.
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 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
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.
The first step is to register the device with the API. You do this from the
didRegisterForRemoteNotificationsWithDeviceToken method in
Here’s how you turn the
Data into an API friendly hex string as an extension.
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
Here’s how I hashed the token with a salt.
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
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
Validate works in combination with the has
Create method above.
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.