Firebase Authenticationの認証情報を維持したままユーザーのuidを変更する

## はじめに
Firebase admin SDKを使うと、Firebase Authenticationで認証されたユーザーのuidを**できるだけ認証情報を維持したまま**、無理やり変更することができました。もしご利用になる場合は、何をやっているか理解した上で、自己責任でやってください。

## 流れ
uidが`before`になっているユーザーを`after`に変えたいとしましょう。やることの大枠は3ステップです。

1. `before`の情報をFirebase Authenticationから取得する
2. `before`をFirebase Authenticationから削除する。`after`がすでに使われている場合は`after`も削除する。
3. 1で取得した`before`の情報を、`after`としてFirebase Authenticationにimportする

2.で`before`や`after`を削除していることに注意してください。

## 実装

今回はTypeScriptで書いていきます。

```TypeScript:changeUid.ts
import * as admin from 'firebase-admin'

const auth = admin.auth()

const beforeUid = 'before'
const afterUid = 'after'

const beforeUserRecord = await auth.getUser(beforeUid)

await auth.deleteUser(beforeUid)
await auth.deleteUser(afterUid) // if need

await auth.importUsers([{
  uid: afterUid,
  email: beforeUserRecord.email,
  phoneNumber: beforeUserRecord.phoneNumber,
  providerData: beforeUserRecord.providerData,
  // set more fields if need
}])
```

キモは`before`の`providerData`、すなわち認証情報を`after`にセットしている部分です。これにより**一部を除く認証情報が引き継がれます**。細かい仕様は[公式ドキュメント](https://firebase.google.com/docs/auth/admin/import-users)をご参照ください。

## 注意点

### パスワード認証は引き継げない
👆で「一部を除く認証情報が引き継がれる」と書きました。上記の実装だとSign in with Apple含めた外部IdPによる認証や電話番号認証は引き継げるものの、メアドパスワード認証（providerDataのproviderIdが`password`のもの）の認証情報は引き継げません。これも引き継げるようにしようと思うと一工夫必要になってきます。

具体的な実装は省きますが勘所を書いておきます。公式ドキュメントの[Import users with Firebase SCRYPT hashed passwords](https://firebase.google.com/docs/auth/admin/import-users#import_users_with_firebase_scrypt_hashed_passwords)も参考になります。

1. importUsersに`passwordHash`やHashアルゴリズムなどの情報を渡す必要があります
2. `passwordHash`は[ドキュメント](https://firebase.google.com/docs/reference/admin/node/admin.auth.UserRecord#optional-passwordhash)によると`auth.listUsers()`で取ってこれると書いてあるが実際にはなぜか取得できないので、CLIで取得する。`firebase auth:export -P your_project_id users.csv`で取得できます。
3. 2で取得したcsvから対象のユーザーのpasswordHashやpasswordSaltを取得する
4. Firebase ConsoleのAuthenticationの三点リーダーを押してパスワードハッシュパラメータを取得し、importUsersに3と合わせて渡す
![スクリーンショット 2020-09-08 10.25.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74853/48124d71-dcb7-a94d-f0c9-db36f0ca0958.png)

### emailやphoneNumberの重複があってもimportできてしまう

[公式ドキュメントのUsage]()にこう書いてあります。Firebase Authenticationはクライアントからのユーザー作成時はemailなどのUniqueであるべきフィールドが重複しているとエラーになります。ですが`importUsers`はその重複チェックを行なってくれないようです。気をつけましょう。

> Importing a user with any other field duplicated (e.g. email) will result in an additional user with the same value. Consequently, when you use this API, you must ensure that you do not duplicate any unique fields.

### クライアントのハンドリングについて

`before`は消えてしまいますので、クライアント側の実装方法によっては予期せぬ状態になってしまうことがあるでしょう。基本的にはログアウトさせて、再度ログインさせるとよいです。uidが`after`になっているはずです。

## おわりに
用法用量を守ってご利用ください。よかったらTwitterフォローしてください👇

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Firebase Authenticationで認証したユーザーのuidを変更する <a href="https://t.co/CoAxXPbJ8p">https://t.co/CoAxXPbJ8p</a> <a href="https://twitter.com/hashtag/Qiita?src=hash&amp;ref_src=twsrc%5Etfw">#Qiita</a> <br>書いた！</p>&mdash; moga🍳 (@_mogaming) <a href="https://twitter.com/_mogaming/status/1303145588137979904?ref_src=twsrc%5Etfw">September 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>