🔗

Firebase Authenticationで匿名認証アカウントを永久アカウント(Googleなど)に紐づける(変換する)

2024/01/19に公開

はじめに

C(consumer)向けのWebアプリケーションの開発においては、アカウントの登録をさせるというのは結構ハードルが高いようで、アカウント登録あり・なしだと、なしのアプリの方が利用者は増えやすいらしい。

そこで、匿名認証ができるFirebase Authenticationを利用してアプリの開発をしていたのだが、ずっと匿名認証ではなく、アカウント登録してくれたらできることが増えるみたいな機能をつけたかった。

公式に匿名アカウントを永久アカウントに変換するという項があり、楽勝でしょと思っていたら案外躓いたので解決方法を備忘録として残しておく。

結論

以下はSvelteKitでの実装例になっているが、ReactでもVueでも同じ。要は、

  1. 匿名認証を行いFirebase Authenticationにユーザーが作成される
  2. auth.currentUserが匿名認証をしたアカウントである状態で、別の永久アカウントのprovider(今回はGoogle)でサインインして、匿名アカウントをGoogleアカウントに紐づける=上書きする(UIDは変わらない)

という感じ。linkWithPopupで匿名アカウントを永久アカウントのproviderに紐づけ(変換)する。

src/routes/+page.svelte
<script>
	import {
		GoogleAuthProvider,
		linkWithPopup,
		signInAnonymously
	} from 'firebase/auth';
	import { page } from '$app/stores';
	
	// `$page.data.firebase.auth`は`getAuth(app)`の戻りの`auth`
	const auth = $page.data.firebase.auth;
	
	const signInAnonymous = async () => {	    
		const userCredential = await signInAnonymously(auth);
		const { user } = userCredential;
		console.log(user);
	};

	const linkAuth = async () => {
		const provider = new GoogleAuthProvider();
		const usercred = await linkWithPopup(auth.currentUser, provider);
		const { user } = usercred;
		//  以下の`user`はsignInAnonymousの`user`と同一のUIDになる
		console.log('Anonymous account successfully upgraded', user);
	};
</script>

<button
	type="button"
	on:click={async () => {
		await signInAnonymous();
	}}
>
	匿名認証
</button>

<button
	type="button"
	on:click={async () => {
		await linkAuth();
	}}
>
	Googleにアカウントを切り替え
</button>

公式の以下の記述がいまいちピンとこずで躓きの原因になってしまった…。紐づけようとしているproviderで認証が完了してFirebase Authenticationにアカウントが作成されないようにしないと、匿名認証のアカウントを永久アカウントに変換できないのでそこが注意。

分かった後に読み返すと、何らかの方法でGoogleでの認証後のIDトークンを取ってそれを使え、という事が言いたいのかなと思った。つまりは、FirebaseのSDKではなく、OpenID Connectのフローを自前で回すなどをしてIDトークンを取得する必要がある、と。それは手間なので今回見たいようなlinkWithPopupを利用するのが手っ取り早いと思われる。

ユーザーがアプリに登録したら、ユーザーの認証プロバイダのログインフローを行います(ただし、いずれかの Auth.signInWith メソッドを呼び出す手前まで)。たとえば、ユーザーの Google ID トークン、Facebook アクセス トークン、メールアドレスとパスワードを取得します。

When the user signs up, complete the sign-in flow for the user's authentication provider up to, but not including, calling one of the Auth.signInWith methods. For example, get the user's Google ID token, Facebook access token, or email address and password.

遭遇したエラー

FirebaseError: Firebase: Error (auth/provider-already-linked).

これはすでにGoogleなどのproviderにリンクされているアカウントを、さらに同じGoogleというproviderに紐づけようとして起きるエラー(多分)。

Googleアカウントで認証しており、auth.currentUserがGoogleアカウントなのに以下のようなコードを実装すると起きる。

import { GoogleAuthProvider, linkWithPopup } from 'firebase/auth';

const provider = new GoogleAuthProvider();
await linkWithPopup(auth.currentUser, provider);

また、匿名アカウントをGoogleアカウントでサインインした後のクレデンシャル情報で紐づけをするような実装のつもりで以下の実装をした場合にも起きる。以下だと、signInWithPopupが成功した時点でauth.currentUserはGoogleアカウントになってしまうので、Googleアカウントに対してGoogleアカウントを紐づけるという意味不明なことになりエラーになる。

	const signInGoogle = async () => {
		const provider = new GoogleAuthProvider();
		const result = await signInWithPopup($page.data.firebase.auth, provider);

		googleCredential = GoogleAuthProvider.credentialFromResult(result);
		const { user: newUser } = await linkWithCredential($page.data.firebase.auth.currentUser, googleCredential);
	};

FirebaseError: Firebase: Error (auth/credential-already-in-use).

匿名認証のアカウントを永久アカウントに紐づけようとしたときに、その永久アカウントのprovider(Googleなど)のアカウントがすでにFirebase Authenticationのアカウントとして存在している場合に起きるエラー。

イメージとしては以下のようにGoogleアカウントがすでにあるのに、匿名認証したアカウントBBBをGoogleアカウントAAAに紐づけようとした、という感じ。

プロバイダ UID
Google AAA
匿名 BBB

Discussion