📱

HTTPv1による認証でiOSアプリのプッシュ通知をFirebaseで実装した話

2025/02/03に公開

はじめに

Firebase Cloud Messaging(FCM)を利用して、iOSアプリでプッシュ通知を実装する際に、HTTPv1 APIを使った認証を導入した。その理由や実装手順について説明する。

なぜHTTPv1 APIを選んだのか?

以前のFCM通知APIでは、**Firebaseプロジェクトのサーバーキーを使った認証(Legacy HTTP API)**が提供されていた。しかし、Googleは2024年にこの方式を廃止し、HTTPv1 APIへの移行を推奨している。

HTTPv1 APIのメリット

  1. IAM(Identity and Access Management)を活用した認証管理ができる。
  2. セキュリティが強化され、プロジェクト単位ではなく、より細かいアクセス制御が可能。
  3. **より詳細な通知設定(データペイロードの拡張)**ができる。

そのため、今回の実装ではHTTPv1 APIを利用した。


ディレクトリ構造と関係ファイル

プッシュ通知に関連するコードは、以下のようなディレクトリ構造で管理した。

📂 SharedShoppingList
 ├── 📂 Firebase
 │    ├── NotificationManager.swift  // 通知の設定と送信を担当
 │    ├── SessionManager.swift       // ユーザーのFCMトークンを管理
 │    ├── AppDelegate.swift          // Firebaseの初期化とAPNs登録
 ├── 📂 CloudFunctions
 │    ├── index.js                   // Firebase Cloud Functionsで通知を送信
 ├── 📂 Views
 │    ├── SettingsView.swift         // 通知設定のUI


Firebase Cloud Messagingの設定手順

1. Firebaseプロジェクトの作成

  1. Firebase Console にアクセスし、新規プロジェクトを作成する。
  2. Cloud Messaging を有効化する。
    • 「プロジェクト設定」>「Cloud Messaging」に移動し、「APNs認証キー」を追加。

2. APNsの設定とFCMの連携

iOSアプリでプッシュ通知を受信するために、**Apple Push Notification Service(APNs)**とFirebaseを統合する。

AppDelegate.swift

import Firebase
import FirebaseMessaging
import UserNotifications

class AppDelegate: NSObject, UIApplicationDelegate, MessagingDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
        FirebaseApp.configure()
        NotificationManager.shared.configure() // 🔥 通知設定
        Messaging.messaging().delegate = self
        return true
    }

    // 🔥 APNsトークンの取得
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        Messaging.messaging().apnsToken = deviceToken
    }
}

このコードを AppDelegate.swift に記述する


3. FirestoreにFCMトークンを保存

ユーザーごとにデバイスのFCMトークンをFirestoreに保存する。

SessionManager.swift

import FirebaseAuth
import FirebaseFirestore
import FirebaseMessaging

class SessionManager: ObservableObject {
    @Published var isLoggedIn: Bool = false

    init() {
        Auth.auth().addStateDidChangeListener { [weak self] _, user in
            if let user = user {
                self?.updateFCMToken() // 🔥 FCMトークンを保存
            }
        }
    }

    func updateFCMToken() {
        Messaging.messaging().token { token, error in
            if let token = token {
                let db = Firestore.firestore()
                let deviceId = UIDevice.current.identifierForVendor?.uuidString ?? "デバイスID"
                db.collection("devices").document(deviceId).setData([
                    "fcmTokens": [token],
                    "userId": Auth.auth().currentUser?.uid ?? "ユーザーID",
                    "lastUpdated": Timestamp(date: Date())
                ], merge: true)
            }
        }
    }
}

このコードを SessionManager.swift に記述する


HTTPv1 APIを使った通知送信

4. Google Cloudの設定(IAMと認証)

  1. Firebase Consoleの「プロジェクト設定」→「サービスアカウント」を開く。
  2. 「新しい秘密鍵を生成」を選択し、JSONファイルをダウンロード。
  3. Google Cloud IAMで、「Firebase Cloud Messaging API」を有効化する。

5. Firebase Cloud Functionsの実装

通知を送るために、Firebase Cloud Functionsを設定する。

index.js

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const { google } = require("googleapis");

admin.initializeApp();

const PROJECT_ID = "プロジェクトID";
const SCOPES = ["<https://www.googleapis.com/auth/firebase.messaging>"];

async function getAccessToken() {
  const auth = new google.auth.GoogleAuth({ scopes: SCOPES });
  const client = await auth.getClient();
  const tokens = await client.getAccessToken();
  return tokens.token;
}

exports.sendPushNotification = functions.https.onRequest(async (req, res) => {
  const { token, title, body } = req.body;
  if (!token || !title || !body) return res.status(400).send("Missing fields");

  try {
    const accessToken = await getAccessToken();
    const message = {
      message: {
        token: token,
        notification: { title, body },
      },
    };

    const response = await fetch(`https://fcm.googleapis.com/v1/projects/${PROJECT_ID}/messages:send`, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(message),
    });

    res.status(200).send(await response.json());
  } catch (error) {
    res.status(500).send("Error sending notification");
  }
});

このコードを CloudFunctions/index.js に記述する


6. iOSアプリから通知を送信

アプリからCloud Functionsのエンドポイントを呼び出して、通知を送る。

NotificationManager.swift

import FirebaseFirestore

class NotificationManager {
    static let shared = NotificationManager()

    func sendNotification(to token: String, title: String, body: String) {
        let url = URL(string: "<https://クラウドファンクションURL>")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        let payload: [String: Any] = [
            "token": token,
            "title": title,
            "body": body
        ]
        request.httpBody = try? JSONSerialization.data(withJSONObject: payload)

        URLSession.shared.dataTask(with: request) { data, _, error in
            if let error = error {
                print("通知送信エラー: \\(error.localizedDescription)")
            }
        }.resume()
    }
}

このコードを Firebase/NotificationManager.swift に記述する


まとめ

  • FirebaseのLegacy HTTP APIが2024年に廃止されるため、HTTPv1 APIを採用した。
  • APNsの設定とFCMトークンの管理を実装した。
  • Firebase Cloud Functionsを用いてサーバー側で通知を送信する仕組みを構築した。
  • iOSアプリからCloud Functionsを呼び出して、通知を送るようにした

この実装により、複数人でリアルタイムで買い物リストの変更を通知できるシステムが構築できた。

Discussion