Firebase App Check と App Attest を使って iOS アプリのバックエンドサービスを保護する

Yuki Suwa
google-cloud-jp
Published in
16 min readDec 6, 2021

この記事は Google Cloud Japan Advent Calendar 2021 の6日目の記事です。Firebase Summit 2021 でリリースされた Firebase App Check と App Attest の連携機能を試してみました。

Firebase App Check とは?

Verifying app identity with App Check (YouTube)

Firebase App Check (以降 App Check) はクライアント(アプリなど)からのリクエストからバックエンドリソースを保護するためのサービスです。

モバイルアプリからバックエンドリソースを呼び出す場合、バックエンドリソースはインターネット経由でアクセスできるようにする必要があります。しかし、呼び出し元のクライアントは想定されているアプリからというわけでは必ずしもありません。悪意のある偽アプリから実行され、データが壊されてしまったり、抜き出されてしまったりする危険性があります。

App Check を使うと、アプリからリクエストを行う際に正規のクライアントであることを証明する証明書を付与することができます。バックエンドリソースはリクエストからこの証明書を参照し、正規のクライアントからのリクエストかどうか判断することができます。

App Check では証明プロバイダをいくつかのオプションから選択します。サポートされている証明プロバイダは次のようにプラットフォームごとに変わってきます。またカスタムプロバイダとして、他のサービスを利用したり独自のプロバイダを作ることもできます。

  • iOS : DeviceCheck または App Attest
  • Android : SafetyNet
  • Web : reCAPTCHA (v3 または Enterprise)

App Check で保護できるリソース

App Check でサポートしている Firebase プロダクトは次の通りです。

  • Realtime Database
  • Firestore ※iOS または Android のみ、Web は近日サポート予定
  • Google Cloud Storage (GCS)
  • Cloud Functions

これら以外のカスタムリソース (API を自分でホスティングしている場合など) を保護することもできます。その場合はアプリで取得できる App Check トークンをカスタム HTTP ヘッダーとして追加し、バックエンドで取得して検証する処理を追加します。詳しくはこちらを参照してください。

App Attest とは?

App Attest は Apple が提供する、バックエンドサービスの保護を目的としたアプリからのリクエストであることを証明するサービスです。DeviceCheck の機能の一部として提供されており、iOS 14 以降で使用できます。

Firebase App Check と App Attest を組み合わせることにより、iOS の標準の保護機能を通して Firebase リソースの保護を行うことができます。

使い方の流れ

基本的な手順はこちらのドキュメントに載っています。

ざっくり説明すると、次の通りです。

  1. Firebase Project と App ID それぞれ作成し、連携するように設定する。
  2. iOS アプリに App Check ライブラリを組み込む。
  3. Firebase コンソールからリクエストのメトリクスを確認する。
  4. 各 Firebase リソースで App Check を有効化する。

設定および実装は Firebase 経由でほぼ行うことができるため、App Attest の実装方法を詳細に知らなくても簡単に導入することができます。

この記事では上記のドキュメントと Codelab を参考にしながら、Firebase Realtime Database の保護に App Check の導入を試してみたいと思います。

Firebase Project と App ID の設定

はじめに Apple Developer で App ID を登録し、Firebase Project に設定を行います。App Attest を使うには Apple Developer の有償ライセンスが必要になりますので、契約している Team Account で試しましょう。

以下は Apple Developer で App ID を登録する画面です。[Capabilities] の中から [App Attest] にチェックを入れて、作成しておきます。ちなみに既に作成されている場合は後から追加できます。

App ID の登録

次に Firebase Project を用意しましょう。この記事では AppAttestSample という名前の Project を作成しました。

Firebase Project の作成

作成したら、次に iOS アプリを追加しましょう。[Apple バンドル ID] には Apple Developer で登録した App ID を設定します。[アプリのニックネーム] と [App Store ID] は省略可能です。設定ファイルのダウンロードで GoogleService-Info.plist をダウンロードしておいてください。

iOS アプリの追加

デモアプリの準備

次にデモアプリを準備します。この記事では Realtime Database のサンプルを、次の Git リポジトリから Clone して使っていきます。

Clone したらサンプルの Xcode Project を開きます。この Project は Swift Package Manager (SPM) を使っているため、初めて Xcode Project を開いた際にライブラリがダウンロードされます。

git clone https://github.com/firebase/quickstart-ios.git
cd quickstart-ios/database/DatabaseExampleSwiftUI/DatabaseExample
open DatabaseExample.xcodeproj

また DatabaseExample フォルダ内に GoogleService-Info.plist がありますがこれはサンプルデータなのでそのままでは使えません。Finder などから先ほどダウンロードした GoogleService-Info.plist に上書きしておきます。また App ID も変更しておきましょう。

Firebase Authentication と Firebase Realtime Database の設定

次にデモアプリで使う Firebase Authentication と Firebase Realtime Database を使える状態にしましょう。

Firebase Authentication の画面で [始める] をクリックして有効化します。

Firebase Authentication を始める

Realtime Database の画面で [データベースを作成] をクリックすると有効化され、データベースが作成できます。

Firebase Realtime Database を作成する

データベース作成フローの中では [ロックモード] を選択します。ルールエディタでは次のルールに書き換えてください。ログイン済みのユーザーのみが投稿を作成できるルールを設定しています。

{
"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"
}
}
}
}
}
}

この状態で、App Check を入れる前の状態のデモアプリが動く状態になりました。iPhone などでデバッグ実行してみてください。

ユーザー登録・ログインすると、記事を投稿することができます。

デモアプリの画面

iOS アプリへの App Check の組み込み

次に App Check を iOS アプリに組み込んでいきましょう。

[プロジェクトの概要] の右の歯車マークをクリックし [プロジェクトの設定] を開きます。[App Check] タブを開き iOS プロジェクトを選択すると [App Attest] の設定画面が表示されます。[チーム ID] に Apple Developer の Team ID を設定し [保存] をクリックします。

App Attest の設定

次に App Check を Realtime Database で[プロダクト] の中から [Realtime Database] を選び [適用] をクリックします。アプリの利用状況に応じた注意事項が表示されます。適用すると拒否されるケースに問題がないか確認し [適用] をクリックします。

App Check の適用

次にアプリのソースコードに手を加えます。

まず Xcode Project を開き [Capabilities] から [App Attest] を追加します。

Capabilities の追加

App Attest の Capability を追加すると DatabaseExample(iOS).entitlements というファイルが生成されます。このファイルを開き App Attest Environment の値を development から production に変更します。

次に SDK を追加します。Targets の [General] の [Framework, Libraries and Embedded Content] の [+] ボタンをクリックして App Check SDK を追加します。

ライブラリの追加

Firebase Package の中に含まれるライブラリは多数あるので [AppCheck] などを検索窓に入れて検索すると手早く見つけられます。

Firebase App Check ライブラリの追加

MyAppCheckProviderFactory.swift という Swift ファイルを新規作成します。この記事では最も簡単な実装方法を紹介していますが、例えば iOS 14 未満 (未サポート) の場合の動作やシミュレータでの動作などを調整したい場合はもう少しコード量が増えます。詳しくはこちらをご参照ください。

class MyAppCheckProviderFactory: NSObject, AppCheckProviderFactory {
func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
return AppAttestProvider(app: app)
}
}

AppDelegate を修正します。 FirebaseApp.configure() が呼び出されている行があると思います。このコードの前に App Check の設定を行うコードを追加します。下記のようなコードになります。

let providerFactory = MyAppCheckProviderFactory()
AppCheck.setAppCheckProviderFactory(providerFactory)
FirebaseApp.configure()

以上で iOS アプリへの導入は完了です。試してみると、iOS デバイス (実機) では動作し、iOS シミュレータでは動作しない形になるかと思います(なお iOS シミュレータは追加の対応を行うと動作させることができますので、必要であればこちらを参考に対応してください)。

リクエストのメトリクスの確認

リクエストのメトリクスは [プロジェクト設定] の [App Check] タブから確認できます。iOS シミュレータは無効なリクエストと判断されるようです。

リクエストのメトリクスの確認

それぞれ次のような意味になります。

  • 確認済みのリクエスト : App Check の有効なトークンが確認されたリクエストです。
  • クライアントリクエストが古い : トークンが確認できていない(ない)リクエストです。概ね App Check SDK が導入される前のバージョンのアプリからの場合です。
  • 送信元が不明なリクエスト : 無効なトークンを使ったリクエストです。盗まれた API キーを使われたり改ざんされたりといった、不正なリクエストである可能性が考えられます。
  • 無効なリクエスト : アプリを偽装したリクエスト、またはエミュレータ・シミュレータによるリクエストです。

まとめ

App Check は無料で使えるため、アプリの不正な利用を防ぐ仕組みとしてこれからリリースするアプリにはまず確実に入れるべきと言えるサービスではないでしょうか。どのような不正リクエストも完全に防げる…とまでは言えませんが、不正利用に対するリスクは確実に軽減できるはずです。

導入時は以下の点に注意してください。

  • リリース済みのアプリに組み込む場合は、古いバージョンのアプリのサポートに注意してください。古いバージョンのアプリは App Check を使用しない、または強制的にバージョンアップさせるなどが必要です。
  • App Attest はもちろん iOS アプリしか対象になりません。Android の場合は SafetyNet、Web の場合は reCAPTCHA を使った導入が必要です。Firebase Project で複数のアプリを登録すると [強制] ができるようになりますが、全てのアプリで App Check を導入してから行うようにしましょう。
  • App Check はユーザー認証に代わるものではありません。ユーザー認証が必要な場合は、この記事のデモアプリのように Firebase Authentication などを使ったユーザー認証を追加で行ってください。

以上です。明日 (7日) は Takao Sakata さんによる GCVE (Google Cloud VMware Engine)に関する記事です。お楽しみに!

--

--

Yuki Suwa
google-cloud-jp

Customer Engineer at Google Cloud Japan. All stories are in my opinion.