After long time looking for documentation about this problem finally I decided write this post to help devs who are in the same situation.
Mongodb adapter is an utility that provides us a "method" to save users/accounts/sessions into Mongodb using providers like Google, Github, etc… But, what happen if you want store your self user data into mongodb ?
At first I was using mongoose package to manage it, but nextauth gives us an implementation that speeds up the process without having to resort to other external libraries like mongoose.
We just have to use MongoClient and write a little utility to keep the conecction alive.
Setup our Adapter
npm install next-auth @next-auth/mongodb-adapter mongodb
Like the documentation says, Mongodb Adapter does not handle the connections automatically, so we have to pass the client connect to the adapter.
The MongoDB adapter does not handle connections automatically, so you will have to make sure that you pass the Adapter a MongoClient that is connected already. Below you can see an example how to do this.
https://authjs.dev/reference/adapter/mongodb
MongoDB Client
// 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: Promise<MongoClient>;
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;
Remember create an environment variable in your .env file with the mongodb uri.
Credentials & Callbacks
You can use the methods given by next-auth adapter to find your user, for example getUserByEmail("your email@gmail.com"). And of course you can take that email from the credentials, getUserByEmail(credentials.username)
import clientPromise from "@/app/lib/mongodb";
import { MongoDBAdapter } from "@next-auth/mongodb-adapter";
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import GitHubProvider from "next-auth/providers/github";
export const authOptions = {
adapter: MongoDBAdapter(clientPromise),
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
CredentialsProvider({
name: "credentials",
credentials: {
username: { label: "Username", type: "text", placeholder: "Aaron" },
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
// Find your user in the database using MongoDBAdapter
const user = await authOptions.adapter.getUser(
"6471f710f772cf139bc5142e"
);
if (user) {
return user;
} else {
return null;
}
},
}),
],
secret: process.env.NEXTAUTH_SECRET,
session: {
// Set it as jwt instead of database
strategy: "jwt",
},
callbacks: {
async jwt({ token, user }) {
// Persist the OAuth access_token and or the user id to the token right after signin
if (user) {
token.accessToken = user.access_token;
token.id = user.id;
}
return token;
},
async session({ session, token }) {
// Send properties to the client, like an access_token and user id from a provider.
session.accessToken = token.accessToken;
session.user.id = token.id;
return session;
},
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
Note that if you want to persist the credentials session, you need to set session.strategy as 'jwt'. Credentials provider does not persist the session in the database.
The Credentials provider can only be used if JSON Web Tokens are enabled for sessions. Users authenticated with the Credentials provider are not persisted in the database.
https://next-auth.js.org/configuration/providers/credentials
Even you could save custom data into the session user. Just pass more options into the session callback.
Session Provider
Remember wrap your layout.js with SessionProvider component to get access the user on the entire app
"use client";
import { SessionProvider } from "next-auth/react";
import "./globals.css";
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<SessionProvider>
<body>{children}</body>
</SessionProvider>
</html>
);
}
Now you can access the url http://localhost:3000/api/auth/signin/credentials and sign in with your credentials or through your provider
After this, you should have access to the session and its properties using useSession hook.
Client side session and from the backend using getServerSession.
import { authOptions } from "../auth/[...nextauth]/route";
import { getServerSession } from "next-auth";
export async function GET(Request) {
const session = await getServerSession(authOptions);
if (session) {
return new Response(JSON.stringify(session), { status: 200 });
} else {
return new Response("Not authenticated user!", { status: 401 });
}
}
Links
Github: https://github.com/aaronaira/next-auth-example-mongodbadapter
Linkedin: https://www.linkedin.com/in/aaron-aira/
Top comments (1)
hey it was nice reading. I wonder if there is any way i can extend the defualt user schema to include other things such as role while sign up. Thank you!