DEV Community

Haruto Hirakawa
Haruto Hirakawa

Posted on

Firebase Auth (v9)で複数のProviderをリンクさせる

firebase/auth は認証周りの機能を提供してくれるSDKです。今回はそれを用いて複数のプロバイダー(GoogleやGithub、Twitterなど)の認証情報を一人のユーザーと結びつける方法の紹介です。

具体的なケース

signInWithPopup という関数が提供されていて、アプリケーションとプロバイダーを引数に渡すことでユーザー認証が行えるようになっています。以下のコードはGoogleアカウントを用いた認証の例です。

import { getAuth, signInWithPopup, GoogleAuthProvider } from 'firebase/auth'
 // appはinitializeAppを用いてconfigファイルを元に生成したもの
import { app } from '../utils/firebase/client'

const provider = new GoogleAuthProvider()
const auth = getAuth(app)

// 認証を行う処理
const handleSignIn = () => {
  signInWithPopup(auth, provider).then(/* ... */)
}
Enter fullscreen mode Exit fullscreen mode

この時点で認証を試してみると、問題なく動きます。さてここに、GitHubアカウントを用いた認証を追加します。引数にProviderを渡せるようにして、Google同様にインスタンスを初期化しておきます。

import { getAuth, signInWithPopup, GoogleAuthProvider, GithubAuthProvider } from 'firebase/auth'

const googleProvider = new GoogleAuthProvider()
const githubProvider = new GithubAuthProvider()

const handleSignIn = (provider: AuthProvider) => {
  signInWithPopup(auth, provider).then(/* ... */)
}
Enter fullscreen mode Exit fullscreen mode

この状態でGitHubでの認証を試みると次のようなエラーが発生します。

auth/account-exists-with-different-credential
Enter fullscreen mode Exit fullscreen mode

一旦エラーハンドリングする

ハンドラでエラーの内容を見てみます。エラーはオブジェクト形式で渡され、codeプロパティにエラーを示す文章が入います。内容は先程と同じ auth/account-exists-with-different-credential が格納されています。

const handleSignIn = (provider: AuthProvider) => {
  signInWithPopup(auth, provider)
    .then(/* ... */)
    .catch(error => {
      console.log(error.code)
    })
}
Enter fullscreen mode Exit fullscreen mode

エラーから認証情報を生成する

OAuthProvider というクラスを用いることでエラーからcredentialを生成できます。失敗した場合はnullが返ってきます。とりあえず生成して早期リターンさせておきます。(エラーが正しい場合は失敗する可能性はないのですが、コンパイラが警告を出してくる場合があるので正しく型とガード節を書いたほうが良いです。)

const credential: AuthCredential | null = OAuthProvider.credentialFromError(error)
if (credential === null) return
Enter fullscreen mode Exit fullscreen mode

次に fetchSignInMethodsForEmail という関数を用いて、メールアドレスから認証情報を取得します。メールアドレスは customData プロパティから取得できます。

const email = error.customData.email
fetchSignInMethodsForEmail(auth, email).then(/* ... */)
Enter fullscreen mode Exit fullscreen mode

resolveされたら、メールアドレスが使用しているメソッドが配列形式で得られます。今回のケースの場合、先にGoogle認証を行ってからGitHubでの認証を試みているので、google.comが出力されるはずです。

fetchSignInMethodsForEmail(auth, email).then((methods: string[]) => {
  console.log(methods[0]) // 認証に用いた方式を取り出せる
})
Enter fullscreen mode Exit fullscreen mode

次に、得られた認証方式の文字列からプロバイダーを得る関数を定義してあげます。ここでreturnしているものは最初に初期化したインスタンスです。

const getProviderById = (id: string) => {
  switch (id) {
    default: {
      return null
    }
    case 'google.com': {
      return googleProvider
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

あとは linkWithCredential 関数を用いてこれらの情報をリンクさせ、同一ユーザーであることをFirebaseに教えてあげます。

.catch((error) => {
  if (error.code === 'auth/account-exists-with-different-credential') {
    const email = error.customData.email
    const credential: AuthCredential | null = OAuthProvider.credentialFromError(error)
    if (credential === null) return

    fetchSignInMethodsForEmail(auth, email).then((methods: string[]) => {
      const provider = getProviderById(methods[0])
      if (provider === null) return
      signInWithPopup(auth, provider).then(result => {
        // 紐付けを行い、以降ログイン時はここまでのフローがスキップできる
        linkWithCredential(result.user, credential)
      })
    })
  }
})
Enter fullscreen mode Exit fullscreen mode

Top comments (0)