Ben Dodson

Freelance iOS, Apple Watch, and Apple TV Developer

Media Library privacy flaw fixed in iOS 10

As I’ve mentioned many times before, iOS had a pretty terrible privacy flaw in that apps didn’t need any permissions in order to read through your media library. This was an issue as it meant you could be fingerprinted easily and tracked across various apps1. Thankfully, this has now been fixed in iOS 102.

In this article, I’ll explain how to update your apps to support this new privacy requirement. Before I do that, I’m going to show you what happens if you run an app built against the iOS 9.3 SDK (or earlier) on iOS 10:

[app name] would like to access Apple Music

You’ll be prompted3 to allow permissions as soon as any media library code is encountered be that on app startup or in a background process such as that used by Music Tracker. If you decline to give permission then the app will quit to the home screen and you will not be able to use it. This is a big change from previous permission switches whereby apps built against the old SDK would be exempt from new permissions (i.e. if your iOS 7 SDK compiled app ran on iOS 8, it wouldn’t crash because it wasn’t using the new location privacy options). Personally I like this change as it allows you to see clearly which apps were abusing these APIs such as Canary and Google Calendar.

Bottom line: If you are using MPMediaQuery or similar in your app, you’d better update it with the iOS 10 SDK as soon as possible as otherwise you are going to get a lot of crashes if your users don’t allow the permission (or a lot of awkward questions if you shouldn’t be using this API).

How do you update your app to request permission for these APIs? First of all, lets whip up a basic example with Swift 3 that will pull the title of the first track in your music library:

@IBAction func buttonPressed(_ sender: AnyObject) {
    let query = MPMediaQuery.songs()
    if let items = query.items, let item = items.first {
        NSLog("Title: \(item.title)")
    }
}

In keeping with other permissions based APIs such as photos, contacts, and calendars, iOS 10 requires that you add a new key to your Info.plist file to explain why you want to use this permission; for music library access, this key is NSAppleMusicUsageDescription. If you don’t add this key, your app will crash as soon as you try and access an MPMediaQuery with the following message:

This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an NSAppleMusicUsageDescription key with a string value explaining to the user how the app uses this data.

With the NSAppleMusicUsageDescription key in place, you will now be given a standard permission dialogue when you first try and access the users media library. If the user chooses “Don’t Allow”, then the media query will fail and any subsequent calls will request in the query.items property being nil. If they choose “OK”, then nothing happens (as execution of the code is not suspended and there is no callback). To fix this, we need to use the MPMediaLibrary.authorizationStatus() and requestAuthorization((MPMediaLibraryAuthorizationStatus) -> Void) APIs that were added in iOS 9.3 to do something like this:

@IBAction func buttonPressed(_ sender: AnyObject) {
    MPMediaLibrary.requestAuthorization { (status) in
        if status == .authorized {
            self.runMediaLibraryQuery()
        } else {
            self.displayMediaLibraryError()
        }
    }
}

func runMediaLibraryQuery() {
    let query = MPMediaQuery.songs()
    if let items = query.items, let item = items.first {
        NSLog("Title: \(item.title)")
    }
}

func displayMediaLibraryError() {
    var error: String
    switch MPMediaLibrary.authorizationStatus() {
    case .restricted:
        error = "Media library access restricted by corporate or parental settings"
    case .denied:
        error = "Media library access denied by user"
    default:
        error = "Unknown error"
    }
    
    let controller = UIAlertController(title: "Error", message: error, preferredStyle: .alert)
    controller.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
    present(controller, animated: true, completion: nil)
}

You first need to request authorization and then run your code only if the status is authorized. If not, then you should display an error specific to whether or not the request was denied or restricted (usually by corporate or parental controls).

I’ve put an example project on GitHub which uses the code above by way of demonstration.

I’m extremely grateful to everyone that has helped share my posts about this issue and to the engineers at Apple for fixing this privacy flaw. I’ll update this post should anything change between now and the expected public release of iOS 10 in September.

  1. Whilst I had no concrete proof of this at the time of writing those articles, it looks like I was right that this was a widespread problem↩︎

  2. I don’t want to take all the credit for this but I honestly don’t think this would have been fixed if I hadn’t raised the issue repeatedly over the last 7 months. ↩︎

  3. In earlier builds of iOS 10 you’d be asked “[App Name] Would Like to Access Apple Music” (as shown in screenshots above) but as of iOS 10 beta 4 this has been changed to the more appropriate “[App Name] Would Like to Access Apple Music and Your Media Library”. ↩︎

The Divide #15 - Virtually Reality » « Talking Shop with Ben Dodson