nextauthjs / nextauthjs/next-auth
Link Account error on existing account_provider_id in Sequelize
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 28.4k
- Forks
- 4k
- PR merge metrics
- No merged PRs in 30d
Description
Adapter type
@next-auth/sequelize-adapter
Environment
System:
OS: Windows 10 10.0.19044
CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
Memory: 3.57 GB / 15.94 GB
Binaries:
Node: 16.15.1 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.18 - ~\AppData\Roaming\npm\yarn.CMD
npm: 8.12.1 - C:\Program Files\nodejs\npm.CMD
Browsers:
Edge: Spartan (44.19041.1266.0), Chromium (104.0.1293.54)
Internet Explorer: 11.0.19041.1566
npmPackages:
next: 12.2.4 => 12.2.4
next-auth: ^4.10.3 => 4.10.3
react: 18.2.0 => 18.2.0
npm WARN config global --global, --local are deprecated. Use --location=global instead.
npmPackages:
@next-auth/sequelize-adapter: ^1.0.5 => 1.0.5
Reproduction URL
https://github.com/wolffparkinson/next-auth-sequelize
Describe the issue
Auth flow would create a duplicate account, if an UNLINKED account with same provider and provider id already exists in the database.
An unlinked account in database could exist due to multiple reasons, one being account created from non-web app sources of a provider. e.g. Discord bots, LINE bots etc.
Following are the debug and sql logs emitted when signin is performed with an unlinked account present in the database.
[next-auth][debug][adapter_getUserByAccount] {
args: [ { providerAccountId: '861488104338096138', provider: 'discord' } ]
}
# user = null here because the account which exists is unlinked
Executing (default): SELECT "id", "type", "provider", "provider_account_id" AS "providerAccountId", "refresh_token", "access_token", "expires_at", "token_type", "scope", "id_token", "session_state", "user_id" AS "userId" FROM "accounts" AS "account" WHERE "account"."provider" = 'discord' AND "account"."provider_account_id" = 'xxxxxxxxxxxxxxxxx' LIMIT 1;
[next-auth][debug][adapter_getUserByAccount] {
args: [ { providerAccountId: 'xxxxxxxxxxxxxxxxxxx', provider: 'discord' } ]
}
Executing (default): SELECT "id", "type", "provider", "provider_account_id" AS "providerAccountId", "refresh_token", "access_token", "expires_at", "token_type", "scope", "id_token", "session_state", "user_id" AS "userId" FROM "accounts" AS "account" WHERE "account"."provider" = 'discord' AND "account"."provider_account_id" = 'xxxxxxxxxxxxxxxxxx' LIMIT 1;
[next-auth][debug][adapter_createUser] {
args: [
{
name: 'xxxxxxxxxx',
email: undefined,
image: 'url-xxxxxxxxxxx-xxxxxxxxxxxxxxx',
emailVerified: null
}
]
}
Executing (default): INSERT INTO "users" ("id","name","email_verified","image") VALUES ($1,$2,$3,$4) RETURNING "id","name","email","email_verified","image";
[next-auth][debug][adapter_linkAccount] {
args: [
{
provider: 'discord',
type: 'oauth',
providerAccountId: 'xxxxxxxxxxxxxxxxxx',
access_token: 'xxxxxxxxxxxx',
expires_at: 1661127660,
refresh_token: 'xxxxxxxxxxx',
scope: 'scope1 scope2',
token_type: 'Bearer',
userId: 'xxx-xxxx-xxxx-xxxx-xxxx'
}
]
}
Executing (default): INSERT INTO "accounts" ("id","type","provider","provider_account_id","refresh_token","access_token","expires_at","token_type","scope","user_id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) RETURNING "id","type","provider","provider_account_id","refresh_token","access_token","expires_at","token_type","scope","id_token","session_state","user_id";
[next-auth][error][adapter_error_linkAccount]
https://next-auth.js.org/errors#adapter_error_linkaccount Validation error {
message: 'Validation error',
name: 'SequelizeUniqueConstraintError'
}
I m not sure if this is same for other sql adapters as well. I m not well versed with other SQL adapters.
The validation error i got here is just because i have unique constraint setup on (provider_id + provider), else it would have gone and created a new account.
getUserByAccount method of sequelize adapter
https://github.com/nextauthjs/next-auth/blob/32f4d5000e46dddd1c17f93bf5456b4ceef8c600/packages/adapter-sequelize/src/index.ts#L115
How to reproduce
- Setup environment variables
- Run the commented out line in
[nextauth].tsonce, this would sync the models and insert an unlinked account.
// ONCE
// sequelize.sync().then(sql=>{
// sql.query(`
// INSERT INTO "accounts" ("id","type","provider","provider_account_id")
// VALUES ($1,$2,$3,$4)
// RETURNING "id","type","provider","provider_account_id";
// `,{bind: [randomUUID(),'oauth',process.env.MY_ACCOUNT_PROVIDER,process.env.MY_ACCOUNT_PROVIDER_ID,]})
// })
- Comment out those lines again and reload
- Login with same account via web
Expected behavior
An already existing unlinked account should be linked back to the user.
One temporary way around is i can manually create a user and link the unlinked account using signIn callback,
but thats like making atleast 2-3 SQL queries again (as adapter would again be making those queries for the flow)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with packages/adapter-sequelize/src/index.ts around getUserByAccount, then follow the sign-in flow described in the reproduction steps. Reproduce with an existing unlinked account and inspect the adapter logs and SQL. Done means the existing account is reused during login without a duplicate insert or SequelizeUniqueConstraintError.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nextjs, typescript
- Domain
- authentication, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100