/ kyokomi note / blog

FirebaseAuthログインまわりで過去に苦戦した件

June 14, 2026

昔、FirebaseAuthのログインまわりでハマったやつを「今ならどうするか」で整理し直したメモ。

結論から先に言うと、SNS連携で credential-already-in-use を踏んで、「Firebase側にだけ取り残されたアカウントをサーバー経由で削除してから連携し直す」リカバリを実装した話。ただこれはその場しのぎで、根っこは「source of truth(ユーザーの真実をどこに持つか)」という設計の問題だったな〜というところに着地した。

ちなみにこの問題、FirebaseAuthを自前のAPIサーバー(リライング・パーティ側、以下RP側)と併用してる構成で起きるやつで、Firebase(+Firestore)で完結してるなら、たぶんそもそも起きない。

何が起きたか

iOSアプリで、既存アカウント(Apple)にGoogleを linkWithCredential で紐付けようとしたら、こうなった。

FIRAuthErrorCodeCredentialAlreadyInUse

意味は「そのGoogle credential、もう別のFirebaseユーザーに紐づいてるよ」って感じ。連携しようとした先が、すでに埋まってる状態。

で、その「すでに紐づいてるFirebaseユーザー」を調べてみると、RP側のDBには存在しない。Firebase側にだけポツンと残った、中身が空っぽのAuthレコードだった。この記事では、迷子アカウント と呼ぶ。

ちなみに名前が似てて意味が違うエラーがあるので、分けておく(地味に混同しがち)。

エラーいつ出る意味
credential-already-in-use(今回)linkWithCredentialその credential が既に別ユーザーに紐づいてる
account-exists-with-different-credentialsignInWithCredential同じメアドで別プロバイダのアカウントが既にある

なぜ迷子アカウントが生まれるのか?

原因は単純で、クライアント主導のフローだからsignInWithCredential を呼んだ瞬間にFirebase側のアカウントができるので、その後のRP側登録がコケると、Firebaseにだけ残る。

sequenceDiagram
    participant C as アプリ
    participant F as Firebase
    participant S as RPサーバー
    C->>F: signInWithCredential
    Note over F: この瞬間にFirebaseアカウント生成
    F-->>C: idToken
    C->>S: idToken を送信
    Note over S: RP側ユーザー作成…の前に失敗 💥
    Note over F: Firebase側にだけ残る = 迷子

採用した手段:迷子アカウントを掃除する

迷子ができた後に掃除するリカバリを、こんな感じで組んだ。

sequenceDiagram
    participant C as アプリ
    participant F as Firebase
    participant S as 迷子削除API
    C->>F: Apple中にGoogleを linkWithCredential
    F-->>C: credential-already-in-use(Gは迷子Bに紐づき)
    C->>F: そのGoogleで signInWithCredential
    F-->>C: 迷子Bにサインイン成功(= 所有権の証明)
    C->>S: BのidTokenをBearerに乗せて削除API
    Note over S: idToken検証 → uid導出 → RP側を確認
    S->>F: RP側にいない=迷子なら Admin SDKで削除
    S-->>C: 削除完了
    C->>F: Appleに入り直して Google を再link

あと、設計でこだわったのは3つ。

とはいえ代償もあって。途中でクライアントのサインイン状態がApple→Bに切り替わり、そのBを消すので、currentUser = 消えたB という状態を一瞬経由する。結果、状態の出入りがやたら多くなり、途中でコケたときのハンドリングが、クライアント実装をかなり複雑にしたのが厳しい〜。

flowchart LR
    A["Apple<br/>ログイン中"] --> B["迷子Bに<br/>サインイン"]
    B --> D["Bを削除<br/>currentUser=消えたB"]
    D --> A2["Appleに<br/>入り直し"]
    A2 --> L["Google再link"]

補足:今回のBは中身が空なので確認なしで淡々と消してOK。これが「両方にデータがある生きたアカウント同士」だとアカウントのマージ(どっちのデータを残すか)という別問題になる。今回は迷子の掃除であってマージじゃない、という区別は意識しとくと混乱しない。

他にどんな道があったか?

今回採用したのが(以下「案1」)なんだけど、せっかくなので選択肢を並べて評価してみる。

案1: クライアント主導のまま掃除する(採用)

今やってるやつ。動くし認可も担保できてる。ただし迷子が生まれる入口は塞いでないので、迷子は今後も生まれ続け、その都度このリカバリが走る。そこは割り切り。

案2: Admin SDK + カスタムトークン主導

クライアントにFirebaseのサインインを直接させず、SNSのトークンを先にサーバーへ渡し、サーバーがOKを出したときだけAdmin SDKでアカウント作成+カスタムトークン発行する方式。これなら迷子は構造的に生まれない。

ただ linkWithCredential に頼れなくなるのが重い。連携が「自DBへのINSERT」になる代わりに「同じメアドなら連携していいの?」の本人性チェックを自前で背負う。ここをミスると乗っ取りで、Firebaseが安全にやってくれてる部分をわざわざ引き取る形になって渋い。個人的にはおすすめしない

案3: Identity Platform + Blocking Functions

beforeCreate フックで、Firebaseアカウントが保存されるにサーバーチェックを挟み、NGなら作成自体を止められる。迷子を構造的に防ぎつつ linkWithCredential はそのまま使えるのが強い。コストはIdentity Platformへのアップグレード(=課金)。大元から直すならこれが本命

まとめ

採用した案1は正直その場しのぎで、ちゃんと大元から直すなら案3が要る(=課金)。迷子の発生が稀だったら案1のリカバリ運用で十分 じゃないかな〜という感想。

Firebaseはほぼ無料で使えて導入も楽で便利だけど、自前APIサーバーと組み合わせた瞬間に「source of truthをどこに置くか」という設計判断を急に迫られる。ここを意識せずチュートリアル通りに進めると、今回みたいに迷子アカウントの掃除に追われることになるんだよね〜。

last modified June 14, 2026