とあるサイトを立ち上げるにあたって、事前に既知のユーザを登録しておき、OAuth認証時に自動的に紐付ける方法に苦戦したのでメモ。
結論
先に結論
ProviderのオプションにallowDangerousEmailAccountLinking: true を追加しましょう。
const options: NextAuthOptions = {
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
allowDangerousEmailAccountLinking: true,
}),
],
...
}
Adapterの改造
さて、最初に何に手を付けていいか分からなかったため、まずはDB周りの処理を疑うことにしました。ユーザを作成するロジックをオーバーライドしてしまえば、どうとでもなると考えました。
で、Adapterを改造すればいいのでは?と思い至ります。
Prismaの場合、next-auth/packages/adapter-prisma/src/index.ts にadapterのファイルがあるのでコレを参考にしましょう。
createUser: (data) => p.user.create({ data }),
getUser: (id) => p.user.findUnique({ where: { id } }),
getUserByEmail: (email) => p.user.findUnique({ where: { email } }),
async getUserByAccount(provider_providerAccountId) {
const account = await p.account.findUnique({
where: { provider_providerAccountId },
select: { user: true },
})
return account?.user ?? null
},
linkAccount: (data) =>
p.account.create({ data }) as unknown as AdapterAccount,
getUserByEmailとかLinkAccountとか、それっぽいメソッドが並んでいます。
処理の流れ的にはgetUserByAccountで紐付けがあるかどうかを確認し、なければ新規ユーザとしてcreateUserする流れになるはず。
ところが実際に実行してみると、同一Emailが登録されているとアカウントのリンクができずにエラーとなります。(Another account already exists with the same e-mail address)
おそらくユーザ作成前にgetUserByEmailでチェックが入っているようです。
該当部分のコードを読んでみると、やはりgetUserByEmailでユーザの取得を行っています。
next-auth/core/lib/callback-handler.js
if (userByEmail) {
const provider = options.provider;
if (provider !== null && provider !== void 0 && provider.allowDangerousEmailAccountLinking) {
user = userByEmail;
} else {
throw new _errors.AccountNotLinkedError("Another account already exists with the same e-mail address");
}
} else {
const {
id: _,
...newUser
} = { ...profile,
emailVerified: null
};
user = await createUser(newUser);
}
が、ここで愕然とします。
>provider.allowDangerousEmailAccountLinking
この設定さえあれば自動で同じEmailのユーザを登録する処理になっているのです。
ということでallowDangerousEmailAccountLinkingで検索したところ、無事に公式の説明が見つかって解決した!というお話でした。
いやーしっかりリファレンスを読まないとダメですねぇ。とはいえキーワードを知ってないとなかなかこのページに至ることもできないので、ソースに当たるというのは結果的に一番の近道かもしれません。
コメント