Firebase App Check for Apple platforms

1. Introduction

Firebase App Check helps protect your backend resources from abuse, such as billing fraud and phishing, by making sure requests come from authentic apps and devices. It works with both Firebase services and your own backend services to keep your resources safe. You can learn more about Firebase App Check in the Firebase documentation.

App Check uses platform-specific services to identify an app, called attestation providers. For Apple development, App Check uses Apple's App Attest service to verify that apps and devices are authentic.

What you'll build

You will:

  • Integrate Firebase App Check with an existing iOS app to protect data in Firebase Realtime Database from abuse
  • Configure the Apple App Attest provider for production and App Check debug attestation providers for development.

What you'll learn

  • How to add Firebase App Check to an existing app
  • How to install different Firebase App Check attestation providers
  • How to configure App Attest for your app
  • How to configure debug attestation providers to test your app on simulators during the app development

What you'll need

2. Get the existing app code

The Firebase quickstart repo contains sample apps to demonstrate different Firebase products. We will use the Firebase Realtime Database quickstart app as a base.

Checkout the Quickstart repository:

git clone https://github.com/firebase/quickstart-ios.git
cd quickstart-ios

Open the Realtime Database SwiftUI Quickstart App Project:

cd database/DatabaseExampleSwiftUI/DatabaseExample
open DatabaseExample.xcodeproj

3. Add App Check dependency to the app targets

  1. Wait for Swift Package Manager to resolve and checkout the dependencies.
  2. Open the Build Phases tab of DatabaseExample (iOS) app target: 74e42ef6ecfc1538.png
  3. Press + button to add FirebaseAppCheck library: a1cf58b36d6cdf1d.png

4. Create and install App Check provider factory

  1. Create a factory class in a separate file, e.g. MyCustomAppCheckProvider.swift:
import Firebase

class MyAppCheckProviderFactory: NSObject, AppCheckProviderFactory {
  func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
    #if targetEnvironment(simulator)
      // App Attest is not available on simulators.
      // Use a debug provider.
      return AppCheckDebugProvider(app: app)
    #else
      // Use App Attest provider on real devices.
      return AppAttestProvider(app: app)
    #endif
  }
}
  1. Set the instance of the MyAppCheckProviderFactory class as an App Check provider factory in the file DatabaseExampleApp.swift.
import Firebase

@main
struct DatabaseExampleApp: App {
  init() {
    // Set an instance of `MyAppCheckProviderFactory` as an App Check 
    // provider factory before configuring Firebase.
    AppCheck.setAppCheckProviderFactory(MyAppCheckProviderFactory())
    FirebaseApp.configure()
  }
  ...
}

5. Create and configure a Firebase project

App Check works with Firebase Authentication to secure Firebase products. For this codelab, we need to follow a few steps in the Firebase console to set up a Firebase project with an iOS app, configure Firebase Authentication, initialize the Realtime Database instance we're going to protect, and configure App Check.

Create a project

First we need to create a Firebase project.

  1. In the Firebase console select Add project.
  2. Give the project a name: App Check Codelab, for example.
  3. Click Create Project.

Create a Realtime Database Instance

Now, navigate to the Realtime Database section of the Firebase console.

  1. Select your new Firebase project and follow the database creation workflow.
  2. Select Locked Mode as the starting mode for your Firebase Security Rules. In the Rules editor, replace the default rules with these:
{
    "rules": {
        // User profiles are only readable/writable by the user who owns it
        "users": {
            "$UID": {
                ".read": "auth.uid == $UID",
                ".write": "auth.uid == $UID"
            }
        },
        // Posts can be read by anyone but only written by logged-in users.
        "posts": {
            ".read": true,
            ".write": "auth.uid != null",
            "$POSTID": {
                // UID must match logged in user and is fixed once set
                "uid": {
                    ".validate": "(data.exists() && data.val() == newData.val()) || newData.val() == auth.uid"
                },
                // User can only update own stars
                "stars": {
                    "$UID": {
                        ".validate": "auth.uid == $UID"
                    }
                }
            }
        },
        // User posts can be read by anyone but only written by the user that owns it,
        // and with a matching UID
        "user-posts": {
            ".read": true,
            "$UID": {
                "$POSTID": {
                    ".write": "auth.uid == $UID",
                    ".validate": "data.exists() || newData.child('uid').val() == auth.uid"
                }
            }
        },
        // Comments can be read by anyone but only written by a logged in user
        "post-comments": {
            ".read": true,
            ".write": "auth.uid != null",
            "$POSTID": {
                "$COMMENTID": {
                    // UID must match logged in user and is fixed once set
                    "uid": {
                        ".validate": "(data.exists() && data.val() == newData.val()) || newData.val() == auth.uid"
                    }
                }
            }
        }
    }
}
  1. Choose a region for the database.
  2. Click Done.

Connect your iOS App

For an in-depth explanation of connecting your app, you can read more about adding Firebase to your iOS project. To get started, follow these main steps:

  1. From the Project Overview screen of your new project, click Add Firebase to your iOS app.
  2. Enter the bundle ID, as "com.google.firebase.codelab.AppCheckCodelab".
  3. Enter the App Store id as "123456".
  4. Click Register App.
  5. Firebase generates a GoogleService-Info.plist file for your app, containing all the necessary Firebase metadata for your app.
  6. In the app registration workflow, click Download GoogleService-Info.plist to download the file.
  7. Copy that file to your application and add it to the AppCheckCodelab target.

Configure Authentication in Firebase Console

Phew! That's quite a bit of setup so far, but hold tight! If you're new to Firebase, you've seen important parts of a workflow that you'll soon be very familiar with.

Now let's configure Authentication for this app.

Enable Authentication Email/Password Sign-in provider

  1. Still in the Firebase console, open the Authentication section of your project.
  2. Select Sign-in methods tab.
  3. Press Add new provider button.
  4. Select Email/Password in the Native providers section.
  5. Enable Email/Password and press Save button.

Add a test user

  1. Open Users tab of the Authentication section.
  2. Press Add user button.
  3. Specify an email and a password for your test user.

Test in the app

Launch the application and try to sign-in with the email and password for the test user you just created. Once signed in, you can try to create a post, post a comment to an existing post and star/unstar posts.

6. Configure App Attest Attestation Provider

Here we will configure the App Check App Attest provider in the Firebase console.

  1. Open Project Settings→ App Check → Your Firebase App → App Attest
  2. Enter the Team ID of your Apple Developer Account: a4303597d7a83e98.png
  3. Press the Save button

Now we have a working Firebase project that is connected to our new app, and App Check is enabled. Now we're ready to configure our specific attestation service! For more about this workflow, see Enable App Check with App Attest on iOS.

7. Configure App Attest for your application

OK, setup is done! Let's get our hands on the Firebase App Check SDK and implement some client code.

Let's configure the Xcode project so that SDK can use Apple's App Attest API to do the attestation, in other words, to check if the app making the request is a real device running the genuine app you've registered.

  1. Make sure you have an explicit App identifier for the app with enabled App Attest capability in your Apple Developer account. Please follow the official Apple documentation if you need help with this process:
  2. Enable App Attest capability for the app in your Apple Developer account.
  3. Add App Attest capability for your app target in the Xcode project:
    • Open Signing & Capabilities tab in your app target settings
    • Press "+ Capability"
    • In the dialog, find and select App Attest capability 368fc785f1b94ed5.png
  4. A file DatabaseExample.entitlements will appear after performing the previous step.
  5. In the DatabaseExample.entitlements file, change the value for the App Attest Environment key to production.

Once you finish these steps and launch the app on a real iOS device (iPhone/iPad), the app will be able to access the Realtime Database again. Note that the iOS Simulator will still not work. See the next section for configuring your development environment.

For reference, to learn more about this enable workflow, see Enable App Check with App Attest on iOS. It's also linked at the end of this codelab.

8. Configure Debug attestation provider for Simulator

Firebase App Check Debug provider enables testing of applications with Firebase App Check enforcement in untrusted environments, including the iOS simulator, during the development process. Let's configure the debug provider together.

Install Firebase Debug Provider in Your App

Option 1: Conditionally create an instance of the debug provider in your factory

We did most of this when we created the App Check provider factory, but now we will need to add logging of the local debug secret generated by the debug provider.

import Firebase

class MyAppCheckProviderFactory: NSObject, AppCheckProviderFactory {
  func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
    #if targetEnvironment(simulator)
      // App Attest is not available on simulators.
      // Use a debug provider.
      let provider = AppCheckDebugProvider(app: app)

      // Print only locally generated token to avoid a valid token leak on CI.
      print("Firebase App Check debug token: \(provider?.localDebugToken() ?? "" )")

      return provider
    #else
      // Use App Attest provider on real devices.
      return AppAttestProvider(app: app)
    #endif
  }
}

This option allows us more flexibility. For instance, you may use other attestation providers like DeviceCheck or custom on OS versions where App Attest is not available. See an example below:

import Firebase

class SimpleAppCheckProviderFactory: NSObject, AppCheckProviderFactory {
  func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
      #if targetEnvironment(simulator)
      // App Attest is not available on simulators.
      // Use a debug provider.
      let provider = AppCheckDebugProvider(app: app)

      // Print only locally generated token to avoid a valid token leak on CI.
      print("Firebase App Check debug token: \(provider?.localDebugToken() ?? "" )")

      return provider
      #else
      if #available(iOS 14.0, *) {
        // Use App Attest provider on real devices.
        return AppAttestProvider(app: app)
      } else {
        return DeviceCheckProvider(app: app)
      }
      #endif
  }
}

Option 2: Install AppCheckDebugProviderFactory

For simpler cases you can temporarily or conditionally install the AppCheckDebugProviderFactory before configuring the Firebase application instance:

#if targetEnvironment(simulator)
let providerFactory = AppCheckDebugProviderFactory()
#else
let providerFactory = MyAppCheckProviderFactory()
#endif

AppCheck.setAppCheckProviderFactory(providerFactory)

FirebaseApp.configure()

This will save you a couple lines of code on creating your own App Check provider factory.

Register debug secret in Firebase console

Get the debug secret from your iOS Simulator

  1. If you choose Option 2: Install AppCheckDebugProviderFactory, enable debug logging for your app by adding -FIRDebugEnabled to the app launch arguments: bd990922f2cde0cf.png
  2. Run your app on a simulator
  3. Find the debug secret in the Xcode console. You can use the console filter to find it faster: 5f15238dd816fffd.png

Register the debug secret

  1. Go to the App Check tab of your project settings.
  2. Open the menu for the Firebase app and select "Manage debug tokens": 631228da2d6680e4.png
  3. Add the secret copied for the Xcode console and press the Save button: 704044dc153b7a0f.png

After these steps, you will be able to use the app on the simulator even with enforced App Check.

As always, more details about this flow can be found in the documentation, at Use App Check with the debug provider on iOS.

9. Enable App Check enforcement for Firebase Realtime Database

For now, our app declares an AppCheckProviderFactory that for real devices returns an AppAttestProvider, so the iOS app does the attestation and sends the results to the Firebase backend. However, the Firebase backend still accepts requests from any device, the iOS Simulator, a script, etc. This mode is useful when you still have users with an old version of your app without App Check and you don't want to enforce access checks yet.

Let's enable App Check enforcement to ensure the Firebase app can be accessed only from genuine devices. Old app versions without App Check integration will stop working once you enable enforcement for the Firebase project.

  1. In the Firebase Console on the App Check tab in the Products section, click on Realtime Database
  2. Press the Enforce button

100c28596a08bb14.png

  1. Read the information in the confirmation pop-up and press the Enforce button

After completing the steps, no applications except yours will be able to access the database any more. Feel free to disable App Check in the app and check it yourself!

10. Congratulations!

6df51ba4ada08168.gif

Now you know how to:

  • Add App Check to an existing project
  • Configure App Attest attestation provider for production version of the app
  • Configure debug attestation provider to test you app on a simulator
  • Observe the app version rollout to know when to enforce App Check for your Firebase project
  • Enable App Check enforcement.

The set up described in this codelab will work for most of the cases, but App Check allows you more flexibility if needed.

These are other resources you may find helpful