next-authで既存ユーザにアカウントを紐付ける(javascript)

JavaScript

とあるサイトを立ち上げるにあたって、事前に既知のユーザを登録しておき、OAuth認証時に自動的に紐付ける方法に苦戦したのでメモ。

結論

先に結論
ProviderのオプションにallowDangerousEmailAccountLinking: true を追加しましょう。

const options: NextAuthOptions = {
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
      allowDangerousEmailAccountLinking: true,
    }),
  ],
  ...
}
OAuth | NextAuth.js
AuthenticationProvidersinNextAuth.jsareOAuthdefinitionsthatallowyouruserstosigninwiththeirfavoritepreexistinglogins.Youcanuseanyofourmanypredefinedproviders,orw...

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で検索したところ、無事に公式の説明が見つかって解決した!というお話でした。
いやーしっかりリファレンスを読まないとダメですねぇ。とはいえキーワードを知ってないとなかなかこのページに至ることもできないので、ソースに当たるというのは結果的に一番の近道かもしれません。

コメント