NextAuthjs, Mongodb, Typescript con Credentials
Hoy vamos a aprender cómo se puede implementar la autenticación en una aplicación Next.js utilizando NextAuth.js con MongoDB como nuestra base de datos y TypeScript para añadir un tipado fuerte a nuestro proyecto. Además, para hacerlo aún más interesante, utilizaremos el Credentials Provider de NextAuth.js, lo que nos permitirá personalizar nuestro propio sistema de autenticación basado en credenciales (es decir permitir al usario que se autentique con correo electronico, contraseña, username, etc).
Para los no iniciados, NextAuth.js es una biblioteca de autenticación completa para Next.js que simplifica enormemente el proceso de autenticación. MongoDB, por otro lado, es una base de datos de documentos, que nos permite trabajar con datos de una manera muy flexible y escalable. Y, por último, TypeScript es una extensión de JavaScript que agrega tipado estático y objetos basados en clases, lo que puede hacer que nuestro código sea más robusto y fácil de depurar.
El Credentials Provider es una funcionalidad de NextAuth.js que nos permite definir nuestra propia lógica de autenticación. Esto significa que no estamos limitados a utilizar los proveedores OAuth predefinidos, sino que podemos implementar nuestro propio sistema de autenticación con nombre de usuario y contraseña, o cualquier otra lógica que necesitemos.
En este tutorial, cubriremos todos los aspectos esenciales para configurar NextAuth.js con MongoDB y TypeScript, desde la configuración inicial hasta la definición de nuestra lógica de autenticación con el Credentials Provider.
Requerimientos
Para seguir este tutorial y obtener el máximo provecho de él, necesitarás lo siguiente:
- Conocimiento básico de JavaScript y React.js: Este tutorial asume que tienes una comprensión sólida de JavaScript y al menos alguna familiaridad con React.js. No necesitas ser un experto, pero deberías estar cómodo con conceptos como componentes, props, y estado.
- Familiaridad con TypeScript: Usaremos TypeScript para añadir un tipado fuerte a nuestra aplicación. No necesitas ser un experto en TypeScript, pero deberías tener al menos una comprensión básica de cómo funciona y por qué se usa.
- Conocimiento básico de Next.js: Idealmente, deberías haber construido al menos una aplicación simple con Next.js antes de este tutorial. Si no lo has hecho, te recomendamos que te familiarices con él, ya que es la base de nuestra aplicación.
- Conocimiento básico de MongoDB: Usaremos MongoDB como nuestra base de datos, así que es importante que entiendas cómo funciona y cómo interactuar con él. No necesitas ser un experto, pero deberías estar cómodo creando, leyendo, actualizando y eliminando documentos en una base de datos MongoDB.
- Node.js y npm instalados: Necesitarás tener Node.js y npm (que viene con Node.js) instalados en tu máquina. Estos son esenciales para el desarrollo de aplicaciones JavaScript modernas.
Si no quieres instalar Mongodb Localmente, tambien puedes usar Mongodb Atlas, para crear tu base de datos en la nube sin tener que instalar nada.
Una vez que hayas reunido estos requisitos, estarás listo para empezar a aprender cómo implementar la autenticación en tu aplicación Next.js con NextAuth.js, MongoDB y TypeScript. ¡Empecemos!
Creacion del proyecto de Backend
Empecemos creando el proyecto de Nextjs
npx create-next-app next-auth-mongodb
Estas son las opciones que he usado para crear este proyecto:
√ Would you like to use TypeScript with this project? ... Yes
√ Would you like to use ESLint with this project? ... Yes
√ Would you like to use Tailwind CSS with this project? ... Yes
√ Would you like to use `src/` directory with this project? ... Yes
√ Use App Router (recommended)? ... Yes
√ Would you like to customize the default import alias? ... No
Una vez creado tambien instalaremos las siguientes dependencias, incluyendo next-auth (NextAuthjs), ejecuta el siguiente comando:
npm i axios mongoose next-auth bcryptjs
Y tambien es necesario instalar estos pero como dependencias de desarrollo:
npm i -D @types/bcryptjs
Si no tienes mucha idea de que son estos modulos, esta es una breve descripcion de algunos de estos:
- axios: Axios es una biblioteca muy popular en JavaScript que se utiliza para hacer solicitudes HTTP desde el navegador y desde Node.js. Ofrece una API muy limpia y fácil de usar para enviar solicitudes HTTP asincrónicas a endpoints REST y realizar operaciones CRUD (Crear, Leer, Actualizar, Borrar).
- mongoose: Mongoose es una biblioteca de JavaScript que proporciona una solución sencilla basada en esquemas para modelar los datos de tu aplicación en MongoDB. Ofrece funcionalidades para la validación de datos, la creación de consultas, los middleware de documentos, entre otras cosas. Es una herramienta esencial cuando se trabaja con MongoDB en Node.js.
- next-auth: Como mencionamos anteriormente, NextAuth.js es una biblioteca de autenticación fácil de implementar para Next.js. Proporciona soluciones para OAuth, protección contra CSRF, gestión de sesiones y más, todo lo cual es muy útil al implementar la autenticación en una aplicación Next.js.
- bcryptjs: bcrypt.js es una biblioteca de JavaScript para Node.js que se utiliza para hashear y verificar contraseñas. El hasheo de contraseñas es un paso importante en la seguridad de las contraseñas, ya que impide que las contraseñas de los usuarios se almacenen en texto plano. bcrypt.js utiliza la biblioteca de cifrado bcrypt, que es una de las más recomendadas para este propósito.
En conjunto, estos módulos te proporcionarán las herramientas necesarias para realizar solicitudes HTTP, interactuar con tu base de datos MongoDB, manejar la autenticación y la seguridad de las contraseñas en tu aplicación Next.js.
Conexion a Mongodb
Ahora vamos a configurar nuestra aplicacion para que pueda conectarse a Mongodb, primero vamos a crear una variable de entorno donde podamos guardar la cadena de conexion de nuestra base de datos.
En la raiz del proyecto crea un archivo llamado .env.local con el siguiente codigo
MONGODB_URI=mongodb://localhost/nextmongo
En donde
nextmongoes el nombre de tu base de datos.
Luego crea dentro de la carpeta src la siguiente carpeta y archivo src/libs/mongodb.ts con el siguiente contenido:
import mongoose from "mongoose";
const { MONGODB_URI } = process.env;
if (!MONGODB_URI) {
throw new Error("MONGODB_URI must be defined");
}
export const connectDB = async () => {
try {
const { connection } = await mongoose.connect(MONGODB_URI);
if (connection.readyState === 1) {
console.log("MongoDB Connected");
return Promise.resolve(true);
}
} catch (error) {
console.error(error);
return Promise.reject(error);
}
};
Modelo de Usuario
Luego crearemos un modelo de usuario en la ruta src/models/user.ts:
import { Schema, model, models } from "mongoose";
const UserSchema = new Schema({
email: {
type: String,
unique: true,
required: [true, "Email is required"],
match: [
/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/,
"Email is invalid",
],
},
password: {
type: String,
required: [true, "Password is required"],
select: false,
},
fullname: {
type: String,
required: [true, "Fullname is required"],
minLength: [3, "Fullname must be at least 3 characters"],
maxLength: [20, "Fullname must be at most 20 characters"],
},
});
const User = models.User || model("User", UserSchema);
export default User;
Creacion de endpoint de Registro
Ahora crearemos un endpoint para registrar usuarios usando los Route Handlers de Nextjs.
Crea un archivo en la ruta app/api/auth/signup/route.ts con el siguiente codigo:
import { connectDB } from "@/libs/mongodb";
import User from "@/models/user";
import { NextResponse } from "next/server";
import bcrypt from "bcryptjs";
import mongoose from "mongoose";
export async function POST(request: Request) {
try {
await connectDB();
const { fullname, email, password } = await request.json();
if (password < 6)
return NextResponse.json(
{ message: "Password must be at least 6 characters" },
{ status: 400 }
);
const userFound = await User.findOne({ email });
if (userFound)
return NextResponse.json(
{
message: "Email already exists",
},
{
status: 409,
}
);
const hashedPassword = await bcrypt.hash(password, 12);
const user = new User({
fullname,
email,
password: hashedPassword,
});
const savedUser = await user.save();
console.log(savedUser)
return NextResponse.json(
{
fullname,
email,
createdAt: savedUser.createdAt,
updatedAt: savedUser.updatedAt,
},
{ status: 201 }
);
} catch (error) {
console.log(error);
if (error instanceof mongoose.Error.ValidationError) {
return NextResponse.json(
{
message: error.message,
},
{
status: 400,
}
);
}
return NextResponse.error();
}
}
Una vez creado puedes hacer una peticion POST, enviando los datos de username, password, y email.
Creacion de Pagina de Registro
Ahora crearemos un formulario de registro en la ruta app/signup/page.tsx:
"use client";
import { FormEvent, useState } from "react";
import axios, { AxiosError } from "axios";
function Signup() {
const [error, setError] = useState();
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
try {
const formData = new FormData(event.currentTarget);
const res = await axios.post("/api/auth/signup", {
email: formData.get("email"),
password: formData.get("password"),
fullname: formData.get("fullname"),
});
console.log(res);
} catch (error) {
console.log(error);
if (error instanceof AxiosError) {
const errorMessage = error.response?.data.message;
setError(errorMessage);
}
}
};
return (
<form onSubmit={handleSubmit}>
{error && <div className="bg-red-500 text-white p-2 mb-2">{error}</div>}
<h1 className="text-3xl font-bold my-2">Signup</h1>
<input
type="text"
placeholder="Fullname"
className="bg-zinc-800 px-4 py-2 block mb-2"
name="fullname"
/>
<input
type="email"
placeholder="Email"
className="bg-zinc-800 px-4 py-2 block mb-2"
name="email"
/>
<input
type="password"
placeholder="Password"
className="bg-zinc-800 px-4 py-2 block mb-2"
name="password"
/>
<button>Signup</button>
</form>
);
}
export default Signup;
Configuracion de NextAuth y Credentials
ahora vamos a configurar a nextauth usando Route Handlers creando un archivo en la ruta /app/api/auth/[...nextauth]/route.ts
import { connectDB } from "@/libs/mongodb";
import User from "@/models/user";
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcryptjs";
const handler = NextAuth({
providers: [
CredentialsProvider({
name: "Credentials",
id: "credentials",
credentials: {
email: { label: "Email", type: "text", placeholder: "jsmith" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
await connectDB();
const userFound = await User.findOne({
email: credentials?.email,
}).select("+password");
if (!userFound) throw new Error("Invalid credentials");
const passwordMatch = await bcrypt.compare(
credentials!.password,
userFound.password
);
if (!passwordMatch) throw new Error("Invalid credentials");
return userFound;
},
}),
],
pages: {
signIn: "/login",
},
});
export { handler as GET, handler as POST };
Providers
import { SessionProvider } from "next-auth/react";
interface Props {
children: React.ReactNode;
}
export default function Providers({ children }: Props) {
return <SessionProvider>{children}</SessionProvider>;
}
en layout.tsx:
import Providers from "./Providers";
import "./globals.css";
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}