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 legitimate 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 verify the integrity of an app and/or device. These services are called attestation providers. One such provider is Apple's App Attest service, which App Check can use to verify the authenticity of Apple apps and devices.

What you'll build

In this codelab, you'll add and enforce App Check in an existing sample application so that the project's Realtime Database is protected from being accessed by illegitimate apps and devices.

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 the debug attestation provider to test your app on Simulators during app development.

What you'll need

  • Xcode 13.3.1 or later
  • An Apple Developer account that allows you to create new app identifiers
  • An iOS/iPadOS device that supports App Attest (learn about App Attest API availability)

2. Get the starter project

The Firebase Quickstarts for iOS repository contains sample apps to demonstrate different Firebase products. You will use the Firebase Database Quickstart app for SwiftUI as a base for this codelab.

Clone the Firebase Quickstarts for iOS repository from the command line:

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

Open the Realtime Database SwiftUI Quickstart app project in Xcode:

cd database/DatabaseExampleSwiftUI/DatabaseExample
xed .

3. Add App Check to your app

  1. Wait for Swift Package Manager to resolve the dependencies of the project.
  2. Open the General tab of the DatabaseExample (iOS) app target. Then, in the Frameworks, Libraries, and Embedded Content section, click the + button.
  3. Select to add FirebaseAppCheck.

4. Create and install the App Check provider factory

  1. In the Shared file group, add a new group named AppCheck.
  2. Inside this group, create a factory class in a separate file, e.g. MyAppCheckProviderFactory.swift , making sure to add it to the DatabaseExample (iOS) target:
    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
      }
    }
    
  3. Next, in DatabaseExampleApp.swift, make sure to import FirebaseAppCheck, and set an instance of the MyAppCheckProviderFactory class as the App Check provider factory.
    import SwiftUI
    import FirebaseCore
    import FirebaseAppCheck
    
    @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

To use App Check in your iOS project, you need to follow these steps in the Firebase console:

  • Set up a Firebase project.
  • Add your iOS app to the Firebase project.
  • Configure Firebase Authentication.
  • Initialize the Realtime Database instance you're going to protect.
  • Configure App Check.

Create a project

First, you need to create a Firebase project.

  1. In the Firebase console, select Add project.
  2. Name your project App Check Codelab
  3. Click Continue.
  4. Disable Google Analytics for this project, and then click Create project.

Create a Realtime Database Instance

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

  1. Click on the Create Database button to start the database creation workflow.
  2. Leave the default location (us-central1) for the database unchanged, and click on Next.
  3. Make sure Locked Mode is selected and click the Enable button to enable the Security Rules for your database.
  4. Navigate to the Rules tab of the Realtime Database browser, and replace the default rules with the following:
    {
        "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"
                        }
                    }
                }
            }
        }
    }
    
  5. Click the Publish button to activate the updated Security Rules.

Prepare your iOS App to be connected to Firebase

To be able to run the sample app on a physical device, you need to add the project to your development team so Xcode can manage the required provisioning profile for you. Follow these steps to add the sample app to your developer account:

  1. In Xcode, select the DatabaseExample project in the project navigator.
  2. Select the DatabaseExample (iOS) target and open the Signing & Capabilities tab.
  3. You should see an error message saying "Signing for DatabaseExample (iOS) requires a development team".
  4. Update the bundle identifier to a unique identifier. The easiest way to achieve this is by using the reverse domain name of your website, for example com.acme.samples.firebase.quickstart.DatabaseExample (please don't use this ID; choose your own, unique ID instead).
  5. Select your development team.
  6. You'll know everything went well when Xcode displays "Provisioning Profile: Xcode Managed Profile" and a little info icon next to this label. Clicking on this icon will display more details about the provisioning profile.

Connect your iOS App

For an in-depth explanation of connecting your app, check out the documentation about adding Firebase to your iOS project. To get started, follow these main steps in the Firebase console:

  1. From the Project Overview screen of your new project, click on the + Add app button and then click on the iOS+ icon to add a new iOS app to your Firebase project.
  2. Enter the bundle ID of your app (use the one you defined in the previous section, such as com.acme.samples.firebase.quickstart.DatabaseExample - keep in mind this must be a unique identifier)
  3. Click Register App.
  4. Firebase generates a GoogleService-Info.plist file containing all the necessary Firebase metadata for your app.
  5. Click Download GoogleService-Info.plist to download the file.
  6. In Xcode, you will see that the project already contains a file named GoogleService-Info.plist. Delete this file first - you will replace it with the one for your own Firebase project in the next step.
  7. Copy the GoogleService-Info.plist file that you downloaded in the previous step into the root folder of your Xcode project and add it to the DatabaseExample (iOS) target, making sure it is named GoogleService-Info.plist
  8. Click through the remaining steps of the registration flow. Since the sample project is already set up correctly, you don't need to make any changes to the code.

Configure Firebase Authentication

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

Now, you will configure Firebase Authentication for this app.

Enable Authentication Email/Password Sign-in provider

  1. Still in the Firebase console, open the Authentication section of the console.
  2. Click Get started to set up Firebase Authentication for your project.
  3. Select the Sign-in method tab.
  4. Select Email/Password in the Native providers section.
  5. Enable Email/Password and click Save.

Add a test user

  1. Open the Users tab of the Authentication section.
  2. Click Add user.
  3. Specify an email and a password for your test user, then click Add user.

Take the app for a spin

Go back to Xcode, and run the application on the iOS Simulator. Sign in with the email and password for the test user you just created. Once signed in, create a post, post a comment to an existing post, and star/unstar posts.

6. Configure an App Attest attestation provider

In this step, you will configure App Check to use the App Attest provider in the Firebase console.

  1. In the Firebase console, navigate to the App Check section of the console.
  2. Click Get started.
  3. In the Apps tab, click on your app to expand its details.
  4. Click App Attest to configure App Attest, then enter the Team ID of your Apple Developer Account (you can find this in the Membership section on the Apple Developer portal): 1645f7a369b678c2.png
  5. Click Save.

With this, you have a working Firebase project that is connected to our new app, and App Check is enabled.

You're now 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

Now it's time to get your hands on the Firebase App Check SDK and implement some client code.

First, you need to configure the Xcode project so that the SDK can use Apple's App Attest API to ensure that requests sent from your app come from legitimate instances of your app.

  1. Add the App Attest capability for your app target in the Xcode project:
  2. open the Signing & Capabilities tab in your app target settings
  3. click the "+" button
  4. in the dialog, find and select App Attest capability ae84cd988a5fab31.png
  5. A file DatabaseExample (iOS).entitlements will appear in the root folder of your Xcode project after performing the previous step.
  6. In the DatabaseExample (iOS).entitlements file, change the value for the App Attest Environment key to production.

Once you finish these steps and launch the app on a physical iOS device (iPhone/iPad), the app will still be able to access the Realtime Database. In a later step, you will enforce App Check, which will block requests being sent from illegitimate apps and devices.

To learn more about this workflow, see Enable App Check with App Attest on iOS.

8. Configure a Debug Attestation Provider for the iOS Simulator

The Firebase App Check Debug provider makes it possible to test applications with Firebase App Check enforcement in untrusted environments, including the iOS Simulator, during the development process. Next, you need to configure the debug provider together.

Install the Firebase debug provider in your app

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

You did most of this when you created the App Check provider factory. In this step, you will add logging of the local debug secret generated by the debug provider, so you can register this instance of the app in the Firebase console for debugging purposes.

Update MyAppCheckProviderFactory.swift with the following code:

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 approach gives us more flexibility for configuring App Check depending on the environment. For instance, you may use other attestation providers like DeviceCheck or a custom attestation provider on OS versions where App Attest is not available. See an example below:

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
      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:

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

  AppCheck.setAppCheckProviderFactory(providerFactory)

  FirebaseApp.configure()
}

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

Register your debug secret in the Firebase console

Get the debug secret from your iOS Simulator

  1. If you chose to install AppCheckDebugProviderFactory (option 2 above), you need to enable debug logging for your app by adding -FIRDebugEnabled to the app launch arguments: f1c6b477a373e144.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: d4c65af93e369c55.png

Note: The debug secret is generated for your simulator on the first app launch and is stored in the user defaults. If you remove the app, reset the simulator or use another simulator, a new debug secret will be generated. Make sure to register the new debug secret.

Register the debug secret

  1. Back in the Firevbase console, go to the App Check section.
  2. In the Apps tab, click on your app to expand its details.
  3. In the overflow menu, select Manage debug tokens: d77c8ff768a00b4b.png
  4. Add the secret that you copied from the Xcode console, and then click Save f845c97b86f694d0.png

After these steps, you can use the app on the Simulator even with App Check enforced.

Note: The debug provider was specifically designed to help prevent the debug secret leaks. With the current approach, you don't need to store the debug secret in your source code.

More details about this flow can be found in the documentation - see 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 returns an AppAttestProvider for real devices. When running on a physical device, your app will perform the attestation and send 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.

Now, you need to enable App Check enforcement to ensure the Firebase app can be accessed only from legitimate devices. Old app versions without App Check integration will stop working once you enable enforcement for the Firebase project.

  1. In the Firebase console in the App Check section, click Realtime Database to expand its details.
  2. Click Enforce.

64e6a81fa979b635.png

  1. Read the information in the confirmation dialog, and then click Enforce.

After completing these steps, only legitimate apps will be able to access the database. All other apps will be blocked.

Try accessing the Realtime Database with an illegitimate app

To see App Check enforcement in action, follow these steps:

  1. Turn off App Check registration by commenting out the App Check registration code in the init method of your app entry point in DatabaseExampleApp.
  2. Reset the Simulator by selecting Device > Erase All Content and Settings. This will wipe the Simulator (and invalidate the device token).
  3. Run the app again on the Simulator.
  4. You should now see the following error message:
    [FirebaseDatabase][I-RDB034005] Firebase Database connection was forcefully killed by the server.  Will not attempt reconnect. Reason: Invalid appcheck token.
    

To re-enable App Check, do the following:

  1. Un-comment the App Check registration code in DatabaseExampleApp.
  2. Restart the app.
  3. Take note of the new App Check token in Xcode's console.
  4. Register the debug token in your app's App Check settings in the Firebase console.
  5. Re-run the app.
  6. You should no longer see an error message, and should be able to add new posts and comments in the app.

10. Congratulations!

9785d32f18b995d2.gif

Now you know how to:

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

Next Steps

Learn how to use Remote Config to gradually rollout App Check to your users in the Gradually roll out Firebase App Check using Firebase Remote Config codelab

These are other resources you may find helpful

The setup described in this codelab will work for most cases, but App Check allows you more flexibility if needed - check out the following links for more details: