10/3

2026

Firebase Auth は Google アカウントなしで使える — Sign in with Apple だけでユーザーを分ける

#Firebase#Firebase Authentication#Sign in with Apple#SwiftUI#iOS#Cloud FunctionsFirebase Auth は Google アカウントなしで使える — Sign in with Apple だけでユーザーを分ける

Firebase Auth は Google アカウントなしで使える

Firebase を使うと聞くと、「利用者に Google でログインしてもらう仕組み」だと思いがちだった。自分もそう思っていた。でも実際は違う。Firebase Authentication は、いろいろなサインインを受け付けて、利用者ごとに uid を配る仕組みだ。Google アカウントは、そのサインインの選び方の1つにすぎない。

iPhone の集中アプリ「DODO」では、Sign in with Apple だけで Firebase Auth を使っている。利用者は Google にログインしないし、Google アカウントも要らない。その仕組みを書く。


Firebase Auth が配るのは「uid」

Firebase Auth の「ログイン方法」には、Google・Apple・メールとパスワード・電話番号・匿名などが並んでいる。どれでサインインしても、Firebase の中では同じように uid を持つユーザーが1人できる。

Sign in with Apple ─┐
Google ログイン   ─┼─→ Firebase Auth ─→ uid(この人の ID)
匿名ログイン      ─┘                   ├─ Firestore のルールで本人だけ読める
                                        └─ サーバーで ID トークンを確かめられる

Google アカウントが要るのは、開発者が Firebase のコンソールや CLI を使うときだけだ。利用者には関係ない。

💡 以前の記事 Firebase 匿名ログインで「とりあえず使える」を実現する の匿名ログインも、同じ考え方。サインインの方法が違うだけで、どれも uid が手に入る。

なぜ Sign in with Apple だけにしたか

DODO では、PC や AI エージェント(Claude Code など)から iPhone にやることを送る機能に、アカウントが要った。送られてきたやることを、その人の所にだけ届けるためだ。

  • iPhone のアプリなので、Face ID で一瞬で終わる Sign in with Apple がいちばん楽。
  • アプリの中でほかのサードパーティのログイン(Google など)を出すと、App Store の決まりで Sign in with Apple も並べる必要がある。なら最初から Apple だけでいい。
  • メールも名前も受け取らない設定にできる。個人の情報を持たずに済む。

コンソールでやること

Firebase コンソールの Authentication → ログイン方法 → Apple を有効にするだけ。

iOS アプリの中でサインインするだけなら、Services ID も要らない(Web でのサインインに使うもの)。bundle ID が Firebase に登録したアプリと合っていればいい。Xcode では、ターゲットに「Sign in with Apple」の Capability を足しておく。

アプリのコード

SwiftUI の SignInWithAppleButton を使う。流れは3つ。

  1. ボタンを押したら、ランダムな nonce を作り、そのハッシュを Apple に渡す。
  2. Apple から返ってきた ID トークンと、元の nonce を Firebase に渡す。
  3. Firebase がそれを確かめて、uid を持つユーザーとしてサインインさせる。
/// SignInWithAppleButton の onRequest で呼ぶ。
func prepare(_ request: ASAuthorizationAppleIDRequest) {
    let nonce = Self.randomNonce()
    currentNonce = nonce
    request.requestedScopes = []          // メールも名前も受け取らない
    request.nonce = Self.sha256(nonce)    // Apple にはハッシュを渡す
}

/// SignInWithAppleButton の onCompletion で呼ぶ。
func complete(_ result: Result<ASAuthorization, Error>) async throws {
    let auth = try result.get()
    guard let credential = auth.credential as? ASAuthorizationAppleIDCredential,
          let tokenData = credential.identityToken, let idToken = String(data: tokenData, encoding: .utf8),
          let nonce = currentNonce else { throw PushInError.signInFailed }
    // Firebase には「元の」nonce を渡す
    let firebaseCredential = OAuthProvider.appleCredential(withIDToken: idToken, rawNonce: nonce, fullName: credential.fullName)
    _ = try await Auth.auth().signIn(with: firebaseCredential)
}
SignInWithAppleButton(.signIn) { service.prepare($0) } onCompletion: { result in
    Task {
        do { try await service.complete(result) } catch { self.error = error.localizedDescription }
    }
}
.frame(height: 44)

nonce は何のためか

nonce は、その場かぎりのランダムな文字列だ。Apple にはハッシュを渡し、Firebase には元の文字列を渡す。Firebase は、Apple の ID トークンの中のハッシュと、元の文字列のハッシュが一致するかを確かめる。これで、どこかで盗まれた ID トークンを使い回してサインインする「リプレイ攻撃」を防げる。

private static func randomNonce(length: Int = 32) -> String {
    let charset = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
    var bytes = [UInt8](repeating: 0, count: length)
    _ = SecRandomCopyBytes(kSecRandomDefault, length, &bytes)
    return String(bytes.map { charset[Int($0) % charset.count] })
}

private static func sha256(_ input: String) -> String {
    SHA256.hash(data: Data(input.utf8)).map { String(format: "%02x", $0) }.joined()
}

メールも名前も受け取らない

request.requestedScopes = [] にすると、Apple はメールアドレスも名前も渡してこない。サインインの画面にも、「名前」「メール」を共有する欄が出ない。

DODO は uid さえあれば十分なので、これにした。持っていない情報は、漏れることもない。プライバシーポリシーにも「メールアドレスと名前は受け取りません」と書ける。

uid でユーザーを分ける

Firestore のルール

アプリは Firestore を直接読む。ルールで「自分の uid の所だけ読める」にしておけば、他の人のデータは見えない。

match /users/{uid}/lists/{id} {
  allow read: if request.auth != null && request.auth.uid == uid;
}
match /{document=**} {
  allow read, write: if false;
}

サーバーでは ID トークンを確かめる

Cloud Functions の API を呼ぶときは、Firebase の ID トークンを付ける。サーバーは Admin SDK でそれを確かめて、uid を取り出す。

// アプリ側
let idToken = try await Auth.auth().currentUser!.getIDToken()
req.setValue("Bearer \(idToken)", forHTTPHeaderField: "Authorization")
// Cloud Functions 側
async function appAuth(req, res, next) {
  const token = bearer(req.header("authorization"));
  if (!token) return res.status(401).json({ error: "missing bearer token" });
  try {
    req.uid = (await getAuth().verifyIdToken(token)).uid;
    next();
  } catch {
    res.status(401).json({ error: "invalid id token" });
  }
}

Apple の ID トークンを直接サーバーで確かめる必要はない。Firebase の ID トークンにまとまっているので、サーバーは Firebase だけを信じればいい。

DODO では、この uid を使って AI エージェント用の API トークンを発行している。そちらの話は Cloud Functions だけでリモート MCP サーバーを作った に書いた。

入れ直しても、同じ uid に戻る

アプリを消して入れ直しても、もう一度「Apple でサインイン」を押せば前と同じ uid に戻る。別の iPhone でも同じだ。

  • Apple は、同じ Apple ID・同じ開発者(チーム)のアプリに対して、いつも同じユーザー ID を返す。
  • Firebase は、その Apple のユーザー ID と Firebase の uid を結びつけて覚えている。

DODO では、入れ直してサインインし直せば、サーバーにある「届いているやること」がそのまま見える。発行した API トークンもサーバー側で uid に結びついているので、Claude Code に登録したトークンもそのまま使える。

同じ uid にならないのは、次のとき。

  • アカウントを削除したとき。Firebase のユーザーごと消すので、次は新しい uid になる。
  • 匿名ログインのとき。匿名ログインは、端末に残っている情報だけで同じ人だと判断する。それが消えれば戻せず、別の端末にも引き継げない。Sign in with Apple との大きな違いだ。

📝 戻るのは、サーバーに置いてあるものだけ。DODO の記録(やった DO・ケツイ・設定)は端末の中だけに保存しているので、アプリを消すと、サインインし直しても戻らない。

アカウントの削除

Sign in with Apple を使うアプリは、アプリの中からアカウントを削除できるようにしないと、App Store の審査で断られる(ガイドライン 5.1.1(v))。削除では3つやる。

  1. サーバーのデータを消す。
  2. Firebase のユーザーを消す。
  3. Apple との連携を取り消す(Sign in with Apple の「このアプリの利用をやめる」と同じ状態にする)。

3 には、その場で Apple から受け取る authorization code が要る。なので、削除のときにもう一度 Sign in with Apple をしてもらい、そのコードで取り消している。

let code = (try? result.get().credential as? ASAuthorizationAppleIDCredential)?
    .authorizationCode.flatMap { String(data: $0, encoding: .utf8) }
try await service.complete(result)                     // サインインし直す
try await service.deleteAccount(authorizationCode: code)

func deleteAccount(authorizationCode: String?) async throws {
    _ = try await call("DELETE", path: "v1/account")   // サーバーのデータと Firebase のユーザーを消す
    if let code = authorizationCode { try? await Auth.auth().revokeToken(withAuthorizationCode: code) }
    signOut()
}

⚠️ revokeToken が Apple に取り消しを頼めるようにするには、Apple Developer で Sign in with Apple 用の**キー(.p8)**を作り、Firebase の Apple の設定の「OAuth コードフローの構成」に、チーム ID・キー ID・秘密鍵を入れておく必要がある。入れていないと、取り消しだけが黙って失敗する。

📝 プレミアムの機能でサインインする作りだと、プレミアムが切れたときに削除の画面へたどり着けなくなりがち。DODO では、削除の欄はプレミアムかどうかに関係なく出すようにした。

まとめ

  • Firebase Auth は「Google ログイン」ではなく、いろいろなサインインを受け付けて uid を配る仕組み。
  • Sign in with Apple だけでも、Firestore のルールとサーバーの ID トークン検証で、ちゃんとユーザーを分けられる。
  • requestedScopes = [] で、メールも名前も受け取らずに済む。
  • 入れ直しても、別の端末でも、サインインし直せば同じ uid に戻る(匿名ログインとの違い)。
  • nonce はハッシュを Apple に、元の文字列を Firebase に。
  • アカウント削除では、Apple との連携の取り消しまでやる(.p8 のキーを忘れずに)。

利用者に Google アカウントを作らせたり、パスワードを覚えさせたりしなくていい。iPhone のアプリなら、これがいちばん手軽だと思う。