I am trying to authenticate using next-auth v4 inside nextjs13. My aim is to store the user’s email, name, image and Id from the auth provider (Google) into a MongoDB database. However, I keep getting error saying "OAuthAccountNotLinked".
The URL on the browser address bar also changes to "http://localhost:3000/login?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Flogin&error=OAuthAccountNotLinked"
Here are my various files:
app/auth/[…nextauth]/route.js file
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import User from "../../models/User";
import bcrypt from "bcryptjs";
import dbConnect from "../../lib/dbConnect";
import GoogleProvider from "next-auth/providers/google";
import { MongoDBAdapter } from "@next-auth/mongodb-adapter";
import clientPromise from "../../lib/mongodb";
const handler = NextAuth({
adapter: MongoDBAdapter(clientPromise),
session: {
strategy: "database",
},
callbacks: {
async signIn({ user, account, profile }) {
console.log("user: ", user);
console.log("account: ", account);
console.log(profile);
return true;
},
},
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
CredentialsProvider({
name: "credentials",
async authorize(credentials, req) {
await dbConnect();
const { email, password } = credentials;
if (!email || !password) {
throw new Error("Email and password required");
}
const user = await User.findOne({ email });
if (!user) {
throw new Error("Invalid Email or password");
}
const isPasswordMatched = await bcrypt.compare(password, user.password);
if (!isPasswordMatched) {
throw new Error("Invalid Email or password");
}
return user;
},
}),
],
pages: {
signIn: "/auth/login",
},
secret: process.env.NEXT_AUTH_SECRET,
});
export { handler as GET, handler as POST };
dbConnect.js
import mongoose from "mongoose";
const MONGODB_URI =
process.env.SERVER === "dev"
? "mongodb://127.0.0.1:27017/mosque-around-me"
: process.env.MONGODB_URI;
console.log(MONGODB_URI);
if (!MONGODB_URI) {
throw new Error(
"Please define the MONGODB_URI environment variable inside .env.local"
);
}
/**
* Global is used here to maintain a cached connection across hot reloads
* in development. This prevents connections growing exponentially
* during API Route usage.
*/
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function dbConnect() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
bufferCommands: true,
};
cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
return mongoose;
});
}
try {
cached.conn = await cached.promise;
} catch (e) {
cached.promise = null;
throw e;
}
return cached.conn;
}
export default dbConnect;
mongodb.js (MongoClient promise)
// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
import { MongoClient } from "mongodb";
if (!process.env.MONGODB_URI) {
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"');
}
const uri = process.env.MONGODB_URI;
const options = {};
let client;
let clientPromise;
if (process.env.NODE_ENV === "development") {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options);
global._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise;
} else {
// In production mode, it's best to not use a global variable.
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
// Export a module-scoped MongoClient promise. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise;
user.js (User model schema):
import mongoose from "mongoose";
import bcrypt from "bcryptjs";
// const jwt = require("jsonwebtoken");
const UserSchema = new mongoose.Schema(
{
name: {
type: String,
minlength: 2,
maxlength: 50,
required: [true, "Please provide first name"],
trim: true,
},
phoneNumber: {
type: String,
},
email: {
type: String,
match: [
/^(([^<>()[]\.,;:s@"]+(.[^<>()[]\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/,
"Please provide a valid email",
],
required: [true, "Please provide an email"],
unique: [true, "Someone is alreay using this email"],
},
authProvider: {
type: String,
required: [true, "Auth provider required"],
},
password: {
type: String,
minlength: 8,
required: [true, "Please provide password"],
},
location: {
type: String,
minlength: 3,
trim: true,
default: "my town",
},
lga: {
type: String,
minlength: 3,
trim: true,
default: "my town",
},
state: {
type: String,
minlength: 3,
trim: true,
default: "my town",
},
country: {
type: String,
minlength: 3,
trim: true,
default: "my town",
},
verified: {
type: Boolean,
default: false,
},
active: {
type: Boolean,
default: true,
},
verificationCode: {
type: String,
length: 4,
},
image: String,
role: {
type: String,
required: [true, "Please provide user role"],
default: "user",
enum: {
values: ["staff", "admin", "user"],
message: "Please select valid role",
},
},
},
{ timestamps: true }
);
// hash the password before saving it
UserSchema.pre("save", async function (next) {
// exit when login with google and facebook
if (!this.password) return;
// exit the function when other fields are updated
if (!this.isModified("password")) return;
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
UserSchema.methods.comparePassword = async function (userPassword) {
const isMatch = await bcrypt.compare(userPassword, this.password);
return isMatch;
};
// export new User model if not created already
export default mongoose.models.User || mongoose.model("User", UserSchema);
2
Answers
from my experience with the library and also from other people’s comments related to this error, the fact that Next-auth handles each provider as a unique user will always create problems since in most cases we want to bind a Google account with the corresponding Facebook account for example, or with another user from a database.
for our next app these are three different users, this makes sense, but we still want to handle this, so errors such as
OAuthAccountNotLinked
are always expected.there is serval similar issues without an optimal solution.
here the error occurred using mongoDB and with only one provider so it can be the same issue as yours.
solution:
if this does not fix the problem, then, it may be, when you are trying to sign in with the "credentials" provider you are already signed in with the "Google auth" provider using the same email address
The solution is:
If u have same emailId stored in database, and ur github is also associated with same email, you will get this error. Try to delete the one in DB, ur problem will be solved.