Skip to content

Commit

Permalink
feat(app-check): android debug token argument for app-check (#6026)
Browse files Browse the repository at this point in the history
* Added FIREBASE_APP_CHECK_DEBUG_TOKEN support on android

* Modfied e2e tests to work with new FIREBASE_APP_CHECK_DEBUG_TOKEN

* Added documentation for FIREBASE_APP_CHECK_DEBUG_TOKEN
  • Loading branch information
Gabriel Lesperance authored Jan 25, 2022
1 parent 1a51dff commit 6f67503
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 58 deletions.
32 changes: 28 additions & 4 deletions docs/app-check/usage/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,35 @@ If unset, the "tokenAutoRefreshEnabled" setting will defer to the app's "automat

The [official documentation](https://firebase.google.com/docs/app-check/web/custom-resource) shows how to use `getToken` to access the current App Check token and then verify it in external services.

## Testing Environments / CI
## Manually Setting Up App Check Debug Token for Testing Environments / CI

### on iOS

App Check may be used in CI environments by following the upstream documentation to configure a debug token shared with your app in the CI environment.

In certain react-native testing scenarios it may be difficult to access the shared secret, but the react-native-firebase testing app for e2e testing does successfully fetch App Check tokens via:
In certain react-native testing scenarios it may be difficult to access the shared secret, but the react-native-firebase testing app for e2e testing does successfully fetch App Check tokens via setting an environment variable and initializing the debug provider before firebase configure in AppDelegate.m for iOS.

### on Android

When using a _release_ build, app-check only works when running on actual Android devices. When using a _debug_ build, you have two ways to run your application / tests with App Check support.

#### A) When testing on an actual android device (debug build)

1. Start your application on the android device.
2. Use `$adb logcat | grep DebugAppCheckProvider` to grab your temporary secret from the android logs. The output should look lit this:

D DebugAppCheckProvider: Enter this debug secret into the allow list in
the Firebase Console for your project: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

3. In the [Project Settings > App Check](https://console.firebase.google.com/project/_/settings/appcheck) section of the Firebase console, choose _Manage debug tokens_ from your app's overflow menu. Then, register the debug token you logged in the previous step.

#### B) Specifying a generated `FIREBASE_APP_CHECK_DEBUG_TOKEN` -- building for CI/CD (debug build)

When you want to test using an Android virtual device -or- when you prefer to (re)use a token of your choice -- e.g. when configuring a CI/CD pipeline -- use the following steps:

1. In the [Project Settings > App Check](https://console.firebase.google.com/project/_/settings/appcheck) section of the Firebase console, choose _Manage debug tokens_ from your app's overflow menu. Then, register a new debug token by clicking the _Add debug token_ button, then _Generate token_.
2. Pass the token you created in the previous step by supplying a `FIREBASE_APP_CHECK_DEBUG_TOKEN` environment variable to the process that build your react-native android app. e.g.:

$ FIREBASE_APP_CHECK_DEBUG_TOKEN="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" react-native run-android

- including the App Check debug test helper in the test app, along with a change to `DetoxTest` for Android
- by setting an environment variable and initializing the debug provider before firebase configure in `AppDelegate.m` for iOS.
Please note that once the android app has successfully passed the app-checks controls on the device, it will keep passing them, whether you rebuild without the secret token or not. To completely reset app-check, you must first uninstall, and then re-build / install.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"tests:packager:jet-reset-cache": "cd tests && cross-env REACT_DEBUGGER=\"echo nope\" node_modules/.bin/react-native start --reset-cache --no-interactive",
"tests:emulator:start": "cd ./.github/workflows/scripts && ./start-firebase-emulator.sh --no-daemon",
"tests:emulator:start-ci": "cd ./.github/workflows/scripts && ./start-firebase-emulator.sh",
"tests:android:build": "cd tests && yarn detox build --configuration android.emu.debug",
"tests:android:build": "cd tests && FIREBASE_APP_CHECK_DEBUG_TOKEN=\"698956B2-187B-49C6-9E25-C3F3530EEBAF\" yarn detox build --configuration android.emu.debug",
"tests:android:build-release": "cd tests && yarn detox build --configuration android.emu.release",
"tests:android:test": "cd tests && yarn detox test --configuration android.emu.debug",
"tests:android:test:debug": "cd tests && yarn detox test --configuration android.emu.debug --inspect",
Expand Down
3 changes: 3 additions & 0 deletions packages/app-check/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ project.ext {
android {
defaultConfig {
multiDexEnabled true
// Here we add a build config field to allow devs to provide a
// FIREBASE_APP_CHECK_DEBUG_TOKEN to be used in debug mode.
buildConfigField "String", "FIREBASE_APP_CHECK_DEBUG_TOKEN", "\"${System.env.FIREBASE_APP_CHECK_DEBUG_TOKEN}\""
}
lintOptions {
disable 'GradleCompatible'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
import com.facebook.react.bridge.*;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.FirebaseApp;
import com.google.firebase.appcheck.AppCheckProviderFactory;
import com.google.firebase.appcheck.FirebaseAppCheck;
import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory;
import com.google.firebase.appcheck.safetynet.SafetyNetAppCheckProviderFactory;
import io.invertase.firebase.common.ReactNativeFirebaseModule;
import java.lang.reflect.*;

public class ReactNativeFirebaseAppCheckModule extends ReactNativeFirebaseModule {
private static final String TAG = "AppCheck";
Expand All @@ -51,7 +53,26 @@ public void activate(
}

if (isDebuggable) {
firebaseAppCheck.installAppCheckProviderFactory(DebugAppCheckProviderFactory.getInstance());

if (BuildConfig.FIREBASE_APP_CHECK_DEBUG_TOKEN != "null") {
// Get DebugAppCheckProviderFactory class
Class<DebugAppCheckProviderFactory> debugACFactoryClass =
DebugAppCheckProviderFactory.class;

// Get the (undocumented) constructor accepting a debug token as string
Class<?>[] argType = {String.class};
Constructor c = debugACFactoryClass.getDeclaredConstructor(argType);

// Create a object containing the constructor arguments
// and initialize a new instance.
Object[] cArgs = {BuildConfig.FIREBASE_APP_CHECK_DEBUG_TOKEN};
firebaseAppCheck.installAppCheckProviderFactory(
(AppCheckProviderFactory) c.newInstance(cArgs));
} else {
firebaseAppCheck.installAppCheckProviderFactory(
DebugAppCheckProviderFactory.getInstance());
}

} else {
firebaseAppCheck.installAppCheckProviderFactory(
SafetyNetAppCheckProviderFactory.getInstance());
Expand Down
19 changes: 1 addition & 18 deletions packages/app-check/e2e/appcheck.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('appCheck()', function () {
});
describe('getToken())', function () {
it('token fetch attempt should work', async function () {
await firebase.appCheck().activate('ignored', false);
// Our tests configure a debug provider with shared secret so we should get a valid token
const { token } = await firebase.appCheck().getToken();
token.should.not.equal('');
Expand Down Expand Up @@ -71,23 +72,5 @@ describe('appCheck()', function () {
return Promise.reject(e);
}
});
// Dynamic providers are not possible on iOS, so the debug provider is always working
it('token fetch attempt should work but fail attestation', async function () {
if (device.getPlatform() === 'android') {
try {
// Activating on Android clobbers the shared secret in the debug provider shared secret, should fail now
await firebase.appCheck().getToken(true);
return Promise.reject(
'Should have thrown after resetting shared secret on debug provider',
);
} catch (e) {
e.message.should.containEql('[appCheck/token-error]');
e.message.should.containEql('App attestation failed');
return Promise.resolve();
}
} else {
this.skip();
}
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,40 +28,8 @@ public class DetoxTest {

@Test
public void runDetoxTests() {

try {
// 1) Detox makes it extremely difficult to pass unencoded arguments directly to the instrumentation runner on Android:
// https://github.com/wix/Detox/issues/2933
//
// 2) AppCheck will only let you set a debug AppCheck token in CI via their test helpers via instrumentation runner args
// https://firebase.google.com/docs/app-check/android/debug-provider#ci
//
// Here we avoid the Detox argument-passing / AppCheck argument-reading difficulty by directly putting the String in.
//
// This is unwanted in nearly all scenarios as it leaks an AppCheck token, but the react-native-firebase test
// project does not have AppCheck set to enforcing, so this is okay for this project.
//
// This has a great potential for leaking your token in a real app that wants to enforce and rely on AppCheck.
Class<?> testHelperClass = DebugAppCheckTestHelper.class;
Method[] methods = testHelperClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals("fromString")) {
Method testHelperMethod = methods[i];
testHelperMethod.setAccessible(true);
DebugAppCheckTestHelper debugAppCheckTestHelper =
(DebugAppCheckTestHelper)testHelperMethod.invoke(null, "698956B2-187B-49C6-9E25-C3F3530EEBAF");
debugAppCheckTestHelper.withDebugProvider(() -> {

// This is the standard Detox androidTest implementation:
Detox.runTests(mActivityRule);

dumpCoverageData();
});
}
}
} catch (Exception e) {
throw new RuntimeException("Unable to force AppCheck debug token: ", e);
}
Detox.runTests(mActivityRule);
dumpCoverageData();
}

// If you send '-e coverage' as part of the instrumentation command line, it should dump coverage as the Instrumentation finishes.
Expand Down

1 comment on commit 6f67503

@vercel
Copy link

@vercel vercel bot commented on 6f67503 Jan 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.