Configurando NextAuth en Next.js 13.4. Parte 3 - Adaptador Mongo
En este articulo explicaré breve y didácticamente como usar los adaptadores de NextAuth para registrar usuarios con MongoDB
Contexto
Última parte de esta seríe enfocada en la autenticación segura de usuarios.
Esta vez utilizaremos MongoDB para registrar a los usuarios que estan iniciando sesion en nuestro sitio y para ello, denuevo usaremos la tecnología de Adapters que nos ofrece NextAuth.
Adapters y NextAuth.
Pueden repasar estos conceptos en: parte 1 y parte 2
MongoDB
Mongo es un sistema de gestión de bases de datos (DBMS) NoSQL (No Structured Query Language) que se utiliza para almacenar y recuperar datos. A diferencia de las bases de datos relacionales tradicionales que utilizan tablas con filas y columnas, MongoDB almacena datos en formato BSON (Binary JSON) en lo que se llama "colecciones".
Teniendo esto en claro ya podemos empezar:
Comencemos!
Paso 1. Instalación del entorno:
Lo primero que haremos es hacer git clone
al repositorio donde se encuentra el codigo del anterior blog: Code y una vez clonado hacer npm i
o yarn
, para instalar los paquetes de la app.
Importante 1: Recuerden cambiar el nombre del archivo .env.example por .env y buscar en google console las credenciales para su proyecto.
Importante 2: Para poder seguir este blog, deberán contar con una cuenta en Mongo Atlas y tener instalado Mongo Compass . Atlas servirá para crear un "cluster" para nuestra app y desde Compass podremos ver lo que pasa en nuestra Base de datos. En caso de no tener conocimientos en Mongo Atlas y/o en Mongo Compass, en el proximo paso 1.5 veremos como configurar esa parte.
Paso 1.5. Crear proyecto en Mongo Atlas y conectarlo con Mongo Compass.
Antes de continuar es importante crear el cluster en donde estarán nuestros datos y también conectarnos a ese servidor mediante Compass, para ver cuales son las distintas base de datos y colecciones que tenemos a disposición.
Estando dentro de Atlas, hacemos click en Projects dentro de nuestra organización (en caso de no tenerla, deben crearla) y luego creamos un nuevo proyecto:
Luego presionan en el boton +Create
En la pantalla que se abre elegimos el plan Free
Le damos a create database y luego se abrirá otra pantalla, que deben completar con sus datos personales.
Crean su usuario, le dan a add my current ip address y luego a finish and close.
En overview le damos a connect
Elegimos como connection security -> Compass
Copiamos el connection string:
Ya podemos conectarnos a nuestra db desde mongoCompass
Pegamos la url conection en el campo URI y deben reemplazar el username, con el username de su db y el password, con el password que generaron al crear la db (si no lo recuerdan pueden ir al Sidebar de mongo Atlas, en security->Database access pueden encontrar esa info).
Si todo fue bien, ya deberían poder ver las base de datos registradas por defecto:
Paso 2. Instalar y configurar MongoDB/Mongoose
Para instalar los servicios de mongo que usaremos en la terminal ejecutamos
terminal
yarn add mongodb mongoose @auth/mongodb-adapter
Ahora crearemos en src (en /src) la carpeta lib.
Dentro creamos la carpeta models y una carpeta con el nombre db.
Por el momento no se preocupen por el código, más adelante lo explicaré al hacer las distintas llamadas a las funciones.
Creamos 2 archivos en db, mongoDBclient.ts y mongoose.ts:
mongoDBclient.ts
// 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("Please add your Mongo URI to .env.local");
}
const uri: string = process.env.MONGODB_URI;
let client: MongoClient;
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).
let globalWithMongoClientPromise = global as typeof globalThis & {
_mongoClientPromise: Promise<MongoClient>;
};
if (!globalWithMongoClientPromise._mongoClientPromise) {
client = new MongoClient(uri);
globalWithMongoClientPromise._mongoClientPromise = client.connect();
}
clientPromise = globalWithMongoClientPromise._mongoClientPromise;
} else {
client = new MongoClient(uri);
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;
mongoose.ts
import mongoose from "mongoose";
let isConnected = false; // Variable to track the connection status
export const connectToDB = async () => {
// Set strict query mode for Mongoose to prevent unknown field queries.
mongoose.set("strictQuery", true);
if (!process.env.MONGODB_URI) return console.log("Missing MongoDB URL");
// If the connection is already established, return without creating a new connection.
if (isConnected) {
console.log("MongoDB connection already established");
return;
}
try {
await mongoose.connect(process.env.MONGODB_URI);
isConnected = true; // Set the connection status to true
console.log("MongoDB connected");
} catch (error) {
console.log(error);
}
};
En models crearemos user.model.ts y agregaremos el siguiente código:
/db/models/user.model.ts
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
image: String
});
const User = mongoose.models.User || mongoose.model("User", userSchema);
export default User;
Ahora debemos actualizar nuestro archivo .env y agregar las siguientes variables de entorno:
MONGODB_URI=
MONGO_USER=
MONGO_PASSWORD=
MONGO_DB=
Importante: pueden encontrar los valores para las variables de entorno en mongo atlas.
en mi caso quedó así:
MONGODB_URI='mongodb+srv://username:Tuq9zkOdff5JTIv0@clusterdb.er6dcdj.mongodb.net/'
MONGO_USER='user'
MONGO_PASSWORD='Tuq1skOdww5JTIv0'
MONGO_DB='auth'
Genial! Ya tenemos la mitad del trabajo hecho, ahora nos falta conectarnos con nuestra base de datos.
Paso 3. MongoAdapter:
Ya tenemos la base de datos lista para ser utilizada y las configuraciones mínimas y necesarias para que NextAuth se comuniqué con nuestra base de datos. Hagamos esa conexión con MongoAdapter.
Lo primero es actualizar el archivo [...nextauth].ts
[...nextauth].ts
import NextAuth from "next-auth";
import { AuthOptions } from "next-auth";
import { MongoDBAdapter } from "@auth/mongodb-adapter";
import clientPromise from "@/lib/db/mongoDBclient";
import GoogleProvider from "next-auth/providers/google";
export const authOptions = {
adapter: MongoDBAdapter(clientPromise, {
databaseName: "auth",
collections: {
Users: "users",
Accounts: "accounts",
Sessions: "sessions"
}
}),
providers: [
GoogleProvider({
clientId: String(process.env.GOOGLE_CLIENT_ID)!,
clientSecret: String(process.env.GOOGLE_CLIENT_SECRET)!
})
],
theme: {
colorScheme: "dark",
logo: "/logo.svg"
},
secret: String(process.env.NEXTAUTH_SECRET)
} as AuthOptions;
export default NextAuth(authOptions);
Como podemos ver en el código, estamos llamando a clientPromise
lo cual hace que nextAuth tenga el acceso necesario para conectarse con MongoDb.
El codigo del archivo mongoose.ts
y user.model.ts
, no será necesario (en el paso 5 tendrá mas sentido tenerlos) ya que nextAuth también se encarga de pasarle a mongo el modelo que tendrá el schema del usuario, pero creo que siempre es interesante hacer ese tipo de configuraciones al comienzo de cualquier desarrollo, ya que si ahora lo desean, pueden listar los usuarios que se irán registrando en su app de forma muy fácil.
Paso 4. Comprobar:
Listo, ya tenemos todo configurado y listo para testear. Lo primero es ejecutar yarn dev o npm run dev, luego ingresar en http://localhost:3000/api/auth/signin y intentar hacer login con el OAuth de google.
Una vez hecho el login, vamos a abrir MongoCompass. Luego, ingresamos la url o uri para ingresar al cluster que creamos en mongoAtlas, y una vez conectados, deberían poder visualizar una base de datos con el nombre auth
:
Como observamos en la imagen, ya tenemos las 3 tablas que generó nextAuth: accounts, sessions y users
. Users contendrá todos los usuarios que iniciaron sesión en nuestra app.
[Extra] Paso 5:, ¿como agregar más propiedades al schema de usuarios?
Si se lo estaban preguntando, además de los campos que nextAuth agrega a la tabla users
, podemos agregar otros datos para diferenciar a nuestros usuarios. Datos como: rol, username o genre, son necesarios en un usuario y podremos agregarlo con una configuración, de una forma muy sencilla, como lo veremos a continuación.
Actualizamos nuestro archivo [...nextauth].ts
[...nextauth].ts
import NextAuth from "next-auth";
import { AuthOptions } from "next-auth";
import { MongoDBAdapter } from "@auth/mongodb-adapter";
import { ObjectId } from "mongodb";
import GoogleProvider from "next-auth/providers/google";
import clientPromise from "@/lib/db/mongoDBclient";
export const authOptions = {
adapter: MongoDBAdapter(clientPromise, {
databaseName: "auth",
collections: {
Users: "users",
Accounts: "accounts",
Sessions: "sessions"
}
}),
providers: [
GoogleProvider({
clientId: String(process.env.GOOGLE_CLIENT_ID)!,
clientSecret: String(process.env.GOOGLE_CLIENT_SECRET)!,
profile(profile) {
return {
id: String(ObjectId.generate()),
name: profile.name,
email: profile.email,
image: profile.picture,
role: "USER"
};
}
})
],
theme: {
colorScheme: "dark",
logo: "/logo.svg"
},
secret: String(process.env.NEXTAUTH_SECRET)
} as AuthOptions;
export default NextAuth(authOptions);
Lo que agregamos fue el parametro "profile" al metodo GoogleProvider. (como dato de curiosidad, si hacen console log a profile, pueden ver todos los datos que OAuth nos otorga del usuario logueado).
Seguiremos utilizando los valores por defecto para crear un usuario y lo único que agregamos es el role que tiene valor por defecto "USER", por lo tanto nuestros usuarios registrados tendrán ese rol y luego, si es necesario, se podrá cambiar más adelante mediante un form.
De esta forma podrán agregar la cantidad de datos que quieran a sus usuarios, lo único importante a tener en cuenta es agregar a su user.model, la nueva propiedad de user: "role".
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
role: {
type: String,
required: true
},
image: String,
});
const User = mongoose.models.User || mongoose.model("User", userSchema);
export default User;
De esta forma sincronizamos las propiedades de la tabla "users" y si necesitamos manipular datos de users desde nuestra app, no tendremos ningún tipo de problema.
Y eso es todo gente 🎊
Por ahora damos por finalizado la serie de Autenticaciones con NextAuth, felicidades si fuiste siguiendo todos los blogs, ya podes hacer autenticaciones seguras a un muy alto nivel y con muy poco código! ¡eso es genial!