Firebase AppCheck on iOS: 403 permission errors - PERMISSION_DENIED

Viewed 2517

Q: How can permission errors be resolved for Firebase App Check?

Background: I have enabled App Check per the documents:

DeviceCheck is enabled/configured per: https://firebase.google.com/docs/app-check/ios/devicecheck-provider

App Attest is enabled configured per: https://firebase.google.com/docs/app-check/ios/devicecheck-provider

SDK is added to the project, with code from: https://github.com/firebase/firebase-ios-sdk/blob/master/FirebaseAppCheck/Apps/FIRAppCheckTestApp/FIRAppCheckTestApp/AppDelegate.swift

Specifically, in appdelegate: Token setup:

FirebaseApp.configure()

requestDeviceCheckToken()

requestDebugToken()

if #available(iOS 14.0, *) {
  requestAppAttestToken()
}

calling:

  // MARK: App Check providers
  func requestDeviceCheckToken() {
    guard let firebaseApp = FirebaseApp.app() else {
      return
    }

    DeviceCheckProvider(app: firebaseApp)?.getToken { token, error in
      if let token = token {
        print("DeviceCheck token: \(token.token), expiration date: \(token.expirationDate)")
      }

      if let error = error {
        print("DeviceCheck error: \((error as NSError).userInfo)")
      }
    }
  }

  func requestDebugToken() {
    guard let firebaseApp = FirebaseApp.app() else {
      return
    }

    if let debugProvider = AppCheckDebugProvider(app: firebaseApp) {
      print("Debug token: \(debugProvider.currentDebugToken())")

      debugProvider.getToken { token, error in
        if let token = token {
          print("Debug FAC token: \(token.token), expiration date: \(token.expirationDate)")
        }

        if let error = error {
          print("Debug error: \(error)")
        }
      }
    }
  }

  @available(iOS 14.0, *)
  func requestAppAttestToken() {
    guard let firebaseApp = FirebaseApp.app() else {
      return
    }

    guard let appAttestProvider = AppAttestProvider(app: firebaseApp) else {
      print("Failed to instantiate AppAttestProvider")
      return
    }

    appAttestProvider.getToken { token, error in
      if let token = token {
        print("App Attest FAC token: \(token.token), expiration date: \(token.expirationDate)")
      }

      if let error = error {
        print("App Attest error: \(error)")
      }
    }
  }

requestDeviceCheckToken()returns a permissions error:

DeviceCheck error: ["NSLocalizedFailureReason": The server responded with an error: 
 - URL: https://firebaseappcheck.googleapis.com/v1beta/projects/<GOOGLE_APP_ID>:exchangeDeviceCheckToken 
 - HTTP status code: 403 
 - Response body: {
  "error": {
    "code": 403,
    "message": "Requests from this iOS client application \u003cempty\u003e are blocked.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "API_KEY_IOS_APP_BLOCKED",
        "domain": "googleapis.com",
        "metadata": {
          "service": "firebaseappcheck.googleapis.com",
          "consumer": "projects/<my project #>"
        }
      }
    ]
  }
}

requestDebugToken() returns a permissions error:

Debug error: Error Domain=com.firebase.appCheck Code=0 "The server responded with an error: 
 - URL: https://firebaseappcheck.googleapis.com/v1beta/projects/<GOOGLE_APP_ID>:exchangeDebugToken 
 - HTTP status code: 403 
 - Response body: {
  "error": {
    "code": 403,
    "message": "Requests from this iOS client application \u003cempty\u003e are blocked.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "API_KEY_IOS_APP_BLOCKED",
        "domain": "googleapis.com",
        "metadata": {
          "consumer": "projects/<my project #>",
          "service": "firebaseappcheck.googleapis.com"
        }
      }
    ]
  }
}
" UserInfo={NSLocalizedFailureReason=The server responded with an error: 
 - URL: https://firebaseappcheck.googleapis.com/v1beta/projects/<GOOGLE_APP_ID>:exchangeDebugToken 
 - HTTP status code: 403 
 - Response body: {
  "error": {
    "code": 403,
    "message": "Requests from this iOS client application \u003cempty\u003e are blocked.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "API_KEY_IOS_APP_BLOCKED",
        "domain": "googleapis.com",
        "metadata": {
          "consumer": "projects/<my project #",
          "service": "firebaseappcheck.googleapis.com"
        }
      }
    ]
  }
}
}

requestAppAttestToken() returns an error:

App Attest error: Error Domain=com.firebase.appCheck Code=0 "(null)"

GCP Console does show all calls to the following w/ 100% errors:

google.firebase.appcheck.v1beta.TokenExchangeService.ExchangeDebugToken 
    google.firebase.appcheck.v1beta.TokenExchangeService.ExchangeDeviceCheckToken   
    google.firebase.appcheck.v1beta.TokenExchangeService.GenerateAppAttestChallenge 

All of which seem to point to a permissions error? Specifically, GOOGLE_APP_ID is in the request URL, but App Check is configured in Firebase via the console...

I'm not seeing anything in the docs or anything obvious in IAM that I missed? :(

Ty in advance for help!

Update After further testing w/ Postman:

The issue seems to be that the SDK isn't passing the X-Ios-Bundle-Identifier correctly when calling AppCheck API(s).

Steps to get to this conclusion:

  • From POSTMAN: API call w/ original API_KEY -> yields initial (above) error response/403
  • From POSTMAN: API call as above, + X-Ios-Bundle-Identifier + valid debug_token -> yields success payload.

So:

  • any ideas to help ID why the X-Ios-Bundle-Identifier isn't being passed by the SDK? The app is using other Firebase API's w/out issue, so seems limited to the AppCheck SDK...
  • and/or - can the X-Ios-Bundle-Identifier be programmatically added (in Swift) to the AppCheck calls (it is properly notated in the .plist)

Resolved!

App Check SDK does not currently support the Android / iOS Application Restriction for API keys. With that, you must remove the App Restriction for your API keys to resolve this issue.

Hopefully, the Application Restriction(s) will be supported at some point...

Update!

v8.8.0-beta now supports the bundle ID! :)

2 Answers

1. Configure the private key for DeviceCheck

  • Make sure you have created a private key for DeviceCheck enter image description here

  • And installed it in firebase project settings under AppCheck tab enter image description here

https://firebase.google.com/docs/app-check/ios/devicecheck-provider

2. Add debug token to firebase.

If you use AppCheckDebugProvider (basically for simulators), after run the project you will see a debug token in the console, you need to copy it and add to AppCheck of the project settings. Than AppCheck will approve it. Also don't forget to add -FIRDebugEnabled for the Arguments Passed on Launch.

https://firebase.google.com/docs/app-check/ios/debug-provider

3. Add production entitlements for AppAttest environment.

The beta version of AppCheck doesn't work with the AppAttest development environment, so you need to setup the production environment in your entitlements. By default, AppAttest works in a development environment, and regardless of your choice in the market it will work with a production.

https://firebase.google.com/docs/app-check/ios/app-attest-provider

https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_devicecheck_appattest-environment

enter image description here

4. Optional:

You can simplify the code

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

AppCheck.setAppCheckProviderFactory (providerFactory)

And getting a token

if let fbApp = FirebaseApp.app () {
    providerFactory.createProvider(with: fbApp)?.getToken { token, error in
        if let token = token {
            print ("AppCheck token: \ (token.token), expiration date: \ (token.expirationDate)")
        } else if let error = error {
            print ("AppCheck error: \ (error as NSError).userInfo)")
        }
    }
}

Or if you want to protect non-firebase resources, you can get a token like this:

AppCheck.appCheck().token (forcingRefresh: false) { token, error in
    if let token = token {
        print ("AppCheck token: \ (token.token), expiration date: \ (token.expirationDate)")
    } else if let error = error {
        print ("AppCheck error: \ (error as NSError).userInfo)")
    }
}

https://firebase.google.com/docs/app-check/ios/custom-resource

App Check SDK does not currently support the Android / iOS Application Restriction for API keys. With that, you must remove the App Restriction for your API keys to resolve this issue.

Hopefully, the Application Restriction(s) will be supported at some point...

Related