Firebase App Checkへの対応について、
Firebase App Distributionで配信したアプリにApp Checkを対応させる際、いくつかハマりポイントがあったので記事にた
PR
実装
Firebase App Checkを実装
Firebase App Checkを実装するとiOSではApp AttestとDeviceCheck、AndroidではPlay Integrityで正常に端末からアクセスされているかを判定できる
FlutterでApp Checkを使用するには、まず* firebase_app_check**パッケージを導入する
次にアプリ側に以下のコードを追加
final appEnv = dotenv.env['APP_ENV']; await FirebaseAppCheck.instance.activate( // Androidの場合はApp Distributionを使用する Play Integrityの認証が成功しないのでdebugの方で実装 androidProvider: appEnv == 'production' ? AndroidProvider.playIntegrity : AndroidProvider.debug, appleProvider: kReleaseMode ? AppleProvider.deviceCheck : AppleProvider.debug, );
Firebase App Distributionでテスト用のアプリを配信している場合は、aabで配信していても通常のPlay Store経由でインストールされたと認識されず、認証時にエラーが発生してしまうため、AndroidProvider.debugを使用して認証するように実装
今回はアプリで取得したApp Checkトークンによって、認証された端末以外からのAPIアクセスを禁止するようにした。バックエンドでApp Checkトークンの認証を行う方法は以下のとおり
まず、アプリ側でApp Checkのトークンを発行し、APIのHeaderに設定
■dart/app/lib/utils/graphql.dart
// App Checkトークンを設定するAuthLink final AuthLink appCheckAuthLink = AuthLink( getToken: () async { final appCheckToken = await getAppCheckToken(); return appCheckToken; }, headerKey: 'X-Firebase-AppCheck', // App Check用 ); final link = authLink.concat(appCheckAuthLink).concat(httpLink);
getAppCheckToken
関数は以下の通り。App Checkトークンは1時間有効なので、キャッシュして利用
■ dart/app/lib/utils/graphql.dart
Future<String?> getAppCheckToken() async { const tokenKey = 'appCheckToken'; const tokenTimeKey = 'tokenFetchTime'; try { final cachedAppCheckToken = await secureStorage.read(key: tokenKey); final tokenFetchTimeString = await secureStorage.read(key: tokenTimeKey); if (cachedAppCheckToken != null && tokenFetchTimeString != null) { final tokenFetchTime = DateTime.parse(tokenFetchTimeString); final currentTime = DateTime.now(); final difference = currentTime.difference(tokenFetchTime); if (difference.inHours < 1) { return cachedAppCheckToken; } } final appCheckToken = await FirebaseAppCheck.instance.getToken(); // ←ここでApp Checkのトークンを取得 if (appCheckToken != null) { await secureStorage.write(key: tokenKey, value: appCheckToken); await secureStorage.write( key: tokenTimeKey, value: DateTime.now().toIso8601String()); return appCheckToken; } } catch (e) { print('Error fetching App Check token: $e'); } return ""; }
この実装により、APIアクセス時にHeaderのX-Firebase-AppCheck
にApp Checkトークンが設定される
バックエンドでは、上記で渡されたX-Firebase-AppCheck
データを以下のコードで認証
■typescript/backend/src/common/guards/auth/auth.guard.ts
if (this.configService.get('NODE_ENV') === 'production') { const appCheckToken = request.headers['x-firebase-appcheck'] if (!appCheckToken) { throw new Error('App Check token not found') } try { await getAppCheck().verifyToken(appCheckToken) } catch (e) { console.log('error', e) throw new Error('Unauthorized (App Check)') } }
この実装により、APIアクセス時にApp Checkトークンが存在しない場合はエラーが表示される
ただし、上記の実装だけでは、実機からのアクセスおよび正式にストアからインストールされたアプリ以外ではAPIが実行できなくなる。開発時にはApp Checkのデバッグトークンを使用して認証を行う。
iOSでデバッグトークンを取得する方法は、Flutter アプリをXcodeで起動
アプリが起動するとXcodeのログにFirebase App Check Debug Token: *****
が出力される
これをFirebaseのコンソールから App Checkのデバッグトークンを登録すれば認証が可能
Android + エミュレータ機能の場合もほぼ同じ。
ローカルでアプリが起動するとログにEnter this debug secret into the allow list in the Firebase Console for your project: *****
が出力される
これをFirebaseコンソールで登録することで、認証が可能になる。
最後に、Firebase App DistributionでAndroidアプリを配布した場合も同様で、Play Store経由でインストールされていないため、Play Integrityの認証が通らず、デバッグトークンが必要になる
以下の手順で取得できる - 1. Firebase App DistributionでAndroidアプリを実機でインストール - 2. PCと Androidの実機をUSBケーブルで接続 - 3. PCのターミナルで以下のコマンドを実行して、Androidのログを出力させる
$ adb logcat | grep "Firebase Console for your project"
これでエミュレータと同様にEnter this debug secret into the allow list in the Firebase Console for your project: *****
の値が出力される。この値をFirebaseのコンソールから App Checkのデバッグトークンとして登録することで認証が可能
これでApp Checkの認証およびデバッグ時の実装が完了した