1. プロジェクト作成と環境準備
手順
- プロジェクト作成:
flutter create my_app_name(スネークケースで!)
- Cursor (VS Code) での準備:
.dartファイルを開かないと右下のステータスバーにFlutterのバージョンやデバイスが出ない(罠)。
- iOSシミュレーター起動:
- リストになければ
open -a Simulatorで強制起動。 - または
xcrun simctl list devices availableで確認。
- リストになければ
💡 ハマりポイント & 疑問
- Q: Hot Reloadが効かない?
- A:
constがついていると更新されない。親のconstを削除するか、Hot Restart (緑の矢印) を使う。
- A:
- Q: APIキー(Geminiなど)はアプリ内に書いていい?
- A: 絶対ダメ。 リバースエンジニアリングでバレる。サーバー(Cloud Functions)経由にするのが定石。
2. Firebaseのセットアップ
手順
- Firebase CLIツールのインストール:
sudo npm install -g firebase-tools --force(権限エラーが出たらsudoと--forceでねじ伏せる)firebase login
- FlutterFire CLIの準備:
dart pub global activate flutterfire_cli(これはDart製のツールを入れるコマンド)
- Flutterと接続:
flutterfire configure(これでlib/firebase_options.dartが自動生成される)
3. Cloud Functions (サーバー側) の実装
手順
- 初期化:
firebase init functions(TypeScript推奨)
- Node.jsバージョンの修正 (重要エラー):
- 初期設定で
Node.js 24が指定されることがあるが、Cloud Functions (Gen 1) は未対応。 functions/package.jsonのenginesを"20"に書き換える。
- 初期設定で
- コード実装 (
index.ts):import * as functions from "firebase-functions/v1";(v1を明示)onCallを使い、if (context.app == undefined)でApp Checkの検証を行う。
- デプロイ:
firebase deploy --only functions
コードの例
import * as functions from "firebase-functions/v1";
// 日本リージョン(tokyo)を指定すると速い
export const helloWorld = functions
.region('asia-northeast1')
.https.onCall((data, context) => {
// ---------------------------------------------------------
// 🛡️ セキュリティチェック (App Check)
// context.app が undefined の場合、App Check のトークンを持っていない不正アクセスとみなす
// ---------------------------------------------------------
if (context.app == undefined) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called from an App Check verified app.'
);
}
// ---------------------------------------------------------
// 📝 ここに本来の処理を書く (AI APIを叩くなど)
// ---------------------------------------------------------
console.log("App Check verified! User ID:", context.auth?.uid);
return {
message: "App Check成功!これはセキュアな通信です。",
status: "success"
};
});
4. Flutter (アプリ側) の実装とiOSの壁
手順
- パッケージ追加:
flutter pub add firebase_core firebase_app_check cloud_functions
- iOSバージョンの引き上げ (重要エラー):
- Cloud Functionsなどの最新プラグインは iOS 15.0以上 が必須。
ios/Podfileのplatform :ios, '15.0'のコメントアウトを外して有効化。- その後
cd ios->pod install --repo-update。
💡 疑問
- Q: Podfileだけ変えて、Xcodeの設定(project.pbxproj)はそのままでいいの?
- A: 開発中は動くのでOK。ただしリリース時はXcode側の設定(Deployment Target)も15.0に合わせるのが正解(古いOSの人がDLできないようにするため)。
- Deployment TargetがAppStoreでインストールできるiOSのバージョンの指定となる
コードの例
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_app_check/firebase_app_check.dart';
import 'package:cloud_functions/cloud_functions.dart';
import 'firebase_options.dart'; // flutterfire configure で生成されたファイル
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Firebase初期化
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// 2. App Check有効化
// シミュレーター開発中は debug プロバイダーを使う
await FirebaseAppCheck.instance.activate(
// Androidエミュレーター用
androidProvider: AndroidProvider.debug,
// iOSシミュレーター用 (ここが重要!)
appleProvider: AppleProvider.debug,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App Check Demo',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String _response = "ボタンを押してテスト";
bool _isLoading = false;
Future<void> _callFunction() async {
setState(() {
_isLoading = true;
_response = "通信中...";
});
// 🔍 【デバッグ用】トークンが取れているか確認するログ
// エラーが出たらこのログを見て、FirebaseコンソールにUUIDを登録し直す
try {
final token = await FirebaseAppCheck.instance.getToken(true);
print("APP CHECK TOKEN: $token"); // ← これがログに出ればOK
} catch (e) {
print("APP CHECK TOKEN ERROR: $e");
}
try {
// 3. Cloud Functionsを呼び出す
// ※ regionは functions/src/index.ts で指定したものと合わせる
final result = await FirebaseFunctions.instanceFor(region: 'asia-northeast1')
.httpsCallable('helloWorld')
.call();
setState(() {
_response = result.data['message'];
});
} on FirebaseFunctionsException catch (e) {
// Functions特有のエラー (App Check失敗など)
setState(() {
_response = "Functionsエラー: [${e.code}] ${e.message}";
});
print("Functions Error: ${e.code} / ${e.message}");
} catch (e) {
// その他のエラー (ネットワーク切れなど)
setState(() {
_response = "予期せぬエラー: $e";
});
print("General Error: $e");
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("App Check Test")),
body: Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_response,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 20),
if (_isLoading)
const CircularProgressIndicator()
else
ElevatedButton(
onPressed: _callFunction,
child: const Text("Cloud Functionsを叩く"),
),
],
),
),
),
);
}
}
5. App Check トークンの登録 (最難関)
現象
アプリからFunctionsを叩くと failed-precondition エラーが出る。
解決手順
- デバッグトークンを探す:
- Cursorのデバッグコンソールに出ない場合は、Xcode (
open ios/Runner.xcworkspace) から実行してシステムログを見る。 Firebase App Check: Enter this debug secret...というUUIDを探す。
- Cursorのデバッグコンソールに出ない場合は、Xcode (
- Firebaseコンソールに登録:
- 「App Check」→「アプリ」→「iOS」→「デバッグトークンを管理」に貼り付け。
- App Check APIの有効化 (忘れがち):
- Google Cloud Console で「Firebase App Check API」を ENABLE にする。
- シミュレーターの区別:
- バージョン違い(例:
iOS 18.0と18.2)は別端末扱い。それぞれトークンが違うので注意。
- バージョン違い(例:
6. セキュリティの最終確認
手順
- 正常系: アプリのボタンを押して「成功」と出るか。
- 攻撃シミュレーション:
- ターミナルから
curl(POST) でトークンなしのリクエストを送る。 403 ForbiddenやFAILED_PRECONDITIONが返ってくれば、「URLがバレても攻撃を防げている」 という証明になる。
- ターミナルから
# 攻撃コマンド例
curl -X POST -H "Content-Type: application/json" \
-d '{"data": {}}' \
https://[リージョン]-[プロジェクト].cloudfunctions.net/[関数名]


コメント