I am on Next.js 14 wth next-auth 4, libSQL, and drizzle orm. Signing in only works once (when account & user are not in database), and subsequent sign-ins return OAuthAccountNotLinked error even though I only use GoogleProvider.
import type { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { db } from "@/lib/db";
export const options: NextAuthOptions = {
adapter: DrizzleAdapter(db),
secret: process.env.NEXTAUTH_SECRET,
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code",
},
},
}),
],
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
pages: {
signIn: "/signin",
error: "/signin",
},
};
import {
integer,
sqliteTable,
text,
primaryKey,
} from "drizzle-orm/sqlite-core";
import type { AdapterAccount } from "@auth/core/adapters";
export const users = sqliteTable("user", {
id: text("id").notNull().primaryKey(),
name: text("name"),
email: text("email").notNull(),
emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
image: text("image"),
});
export const accounts = sqliteTable(
"account",
{
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => ({
compoundKey: primaryKey({
columns: [account.provider, account.providerAccountId],
}),
})
);
export const verificationTokens = sqliteTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
})
);
I set allowDangerousLinking
to true in GoogleProvider
options as a workaround, and it does work, but I get this error in the console:
LibsqlError: SQLITE_CONSTRAINT: SQLite error: UNIQUE constraint failed: account.provider, account.providerAccountId
This leads me to believe it's not detecting the linked account and trying to insert another one, even though it already exists.
This fixed it for me: https://github.com/nextauthjs/next-auth/issues/8377#issuecomment-1704299629
Override the getUserByAccount method from DrizzleAdapter and make it async to await the results
function getAdapter(): Adapter {
return {
...DrizzleAdapter(db),
async getUserByAccount(providerAccountId) {
const results = await db
.select()
.from(accounts)
.leftJoin(users, eq(users.id, accounts.userId))
.where(
and(
eq(accounts.provider, providerAccountId.provider),
eq(accounts.providerAccountId, providerAccountId.providerAccountId),
),
)
.get();
return results?.user ?? null;
},
};
}
...
NextAuth({
adapter: getAdapter(),
});