How to hide Fabric API key and build secret in an Open Source project

A mobile app that integrates the Fabric mobile platform and the Crashlytics crash reporting framework requires two things to successfully communicate with the Fabric / Crashlytics server platform:

  • The Fabric API key: This is required at build time so that the build process can upload the debug symbols that later are required to symbolicate crash reports. The API key is also required at runtime so that the app on a customer device can submit crash reports.
  • The Fabric build secret: This is also required for uploading debug symbols at build time.

Whoever knows these two pieces of information can freely interact with the Fabric / Crashlytics server platform on behalf of the app, so obviously you want to restrict knowledge of the information to people who can be trusted. This article shows how to keep API key and build secret private even in an Open Source project like Little Go where, by definition, there is the desire to publish everything.

When you add Fabric / Crashlytics to a project for the first time, either via the Fabric app or via CocoaPods, the onboarding process makes the following changes to your project:

  • It adds a new "Run Script" build phase, which uploads debug symbols as part of the build process, to the Xcode project. The build phase contains both API key and build secret. The build phase, and therefore both the API key and the build secret, are stored in Foo.xcodeproj/project.pbxproj.
  • It also adds the API key to the project's Info.plist file so that the API key is available at runtime.

Because both Foo.xcodeproj/project.pbxproj and Info.plist are managed by source control, in an Open Source project this means that API key and build secret become public as soon as the changes made by the onboarding process are commited to source control history. To prevent API key and build secret from entering source control history, you therefore must make the following manual changes immediately after the onboarding process has completed, i.e. before committing anything to source control.

  1. Create two new files in the project's root folder: fabric.apikey and fabric.buildsecret which, obviously, are used to store the API key and the build secret.
  2. Add the two files to .gitignore (or to the equivalent ignore file respected by your source control software). This makes sure that the two files remain private and will not leave your local computer when you publish changes on GitHub or some other code hosting platform.
  3. Add fabric.apikey to the Xcode project. In Xcode in the "File Inspector" pane, make sure the file is is part of the build target. We want the file to be part of the app bundle that we distribute to customers, so that the API key is available at runtime on the customer's device.
  4. Optionally you can also add fabric.buildsecret to the Xcode project, but if you do this you should make sure in the "File Inspector" pane the file is not part of the build target - the build secret is not needed at runtime.
  5. Replace the "Run Script" build phase (which currently contains a hardcoded API key and build secret) with the following script (which reads the required values from the previously created files):
    FABRIC_APIKEY_FILE="${SRCROOT}/fabric.apikey"
    FABRIC_BUILDSECRET_FILE="${SRCROOT}/fabric.buildsecret"
    
    if test ! -f "$FABRIC_APIKEY_FILE" -o ! -f "$FABRIC_BUILDSECRET_FILE"; then
      echo "This build wants to upload dSYM files to Crashlytics."
      echo "Uploading is possible only if a Fabric API key and a Fabric build secret are"
      echo "available. This build is failing because at least one of these pieces of"
      echo "information is missing."
      echo ""
      echo "To fix the problem, create the following files and store the API key and"
      echo "build secret, respectively, within those files:"
      echo ""
      echo "  $FABRIC_APIKEY_FILE"
      echo "  $FABRIC_BUILDSECRET_FILE"
      echo ""
      echo "If you forked the project then you must register with Crashlytics and"
      echo "get your own API key and build secret."
    
      # Let the build fail
      exit 1
    fi
    
    FABRIC_APIKEY=$(cat "$FABRIC_APIKEY_FILE")
    if test $? -ne 0; then
      echo "Cannot read $FABRIC_APIKEY_FILE"
      exit 1
    fi
    
    FABRIC_BUILDSECRET=$(cat "$FABRIC_BUILDSECRET_FILE")
    if test $? -ne 0; then
      echo "Cannot read $FABRIC_BUILDSECRET_FILE"
      exit 1
    fi
    
    echo "Uploading dSYM files to Crashlytics"
    "${PODS_ROOT}/Fabric.framework/run" "$FABRIC_APIKEY" "$FABRIC_BUILDSECRET"
    
  6. Replace the Fabric startup code in your application delegate with the following code snippet that launches Crashlytics:
    NSURL* resourceURL = [[NSBundle mainBundle] URLForResource:@"fabric.apikey" withExtension:nil];
    NSStringEncoding usedEncoding;
    NSString* fabricAPIKey = [NSString stringWithContentsOfURL:resourceURL usedEncoding:&usedEncoding error:NULL];
    
    // The string that results from reading the bundle resource contains a trailing
    // newline character, which we must remove now because Fabric/Crashlytics
    // can't handle extraneous whitespace.
    NSCharacterSet* whitespaceToTrim = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    NSString* fabricAPIKeyTrimmed = [fabricAPIKey stringByTrimmingCharactersInSet:whitespaceToTrim];
    
    [Crashlytics startWithAPIKey:fabricAPIKeyTrimmed];
    
  7. Finally, remove the API key from Info.plist. It is no longer needed because the Crashlytics launch code now reads the API key from a separate bundle resource.

Build, run & enjoy. Check out Little Go on GitHub to see a working example.

Note that if you ever lose the API key and/or build secret you can retrieve them from the Fabric website: Log in > Settings > Organizations > Select organization. On the organization page there are two links to show the API key and the build secret.

References:

Comments

One line of "Run Script" is missing an ending quote -- be warned

THIS LINE: echo "If you forked the project then you must register with Crashlytics and

...is missing an ending quote, fyi.

Thanks for pointing this out…

Thanks for pointing this out! I have now added the end quote.