Autenticação NextAuth.js com MongoDB
Ahmed Bouchefra10 min read • Published Aug 30, 2024 • Updated Aug 30, 2024
APLICATIVO COMPLETO
Avalie esse Tutorial
NextAuth.js é uma biblioteca de autenticação robusta construída para aplicativos Next.js. O NextAuth.js simplifica a integração de funcionalidades de autenticação e oferece compatibilidade com vários métodos de autenticação, incluindo provedores OAuth (Google, Github etc.), logins por e-mail/senha e muito mais.
Este tutorial mostra como configurar o NextAuth.js para autenticação de usuário com e-mail e senha em seu aplicativo Next.js 14 .
Next-Auth é uma biblioteca de autenticação projetada para aplicativos Next.js especificamente. Simplificando, o Next-Auth facilita a adição de autenticação aos seus aplicativos Next.js. Ele fornece uma integração mais perfeita das funcionalidades de autenticação. O Next-Auth também oferece melhor compatibilidade com outros métodos de autenticação, como provedores OAuth (Google, Github, etc.).
O Next-Auth oferece um conjunto de APIs fáceis de usar e outros componentes que podem lidar com fluxos de autenticação muito complexos. Isso significa que os desenvolvedores podem se concentrar mais na criação de seus aplicativos.
O Next-Auth oferece pronto para uso, segurança de autenticação e gerenciamento de sessão integrado. É apropriado para aplicativos de todos os tamanhos e funciona bem com ambientes de computação de borda e sem servidor. Ele também oferece suporte completo ao Typescript , para uma melhor experiência do desenvolvedor.
Antes de começarmos, certifique-se de ter os seguintes pré-requisitos:
- Node.js v20: Verifique se você tem o Node.js instalado em sua máquina. Você pode baixá-lo do site oficial.
- React e Next.js 14: uma compreensão básica do React e Next.js 14 é útil para este tutorial.
Certifique-se de que o serviço mongodb-community foi iniciado localmente. Como alternativa, você pode usar um MongoDB Atlas clustergratuito para sempre.
Vamos criar um novo projeto Next.js usando o seguinte comando no seu terminal:
1 npx create-next-app@latest mongodb-auth
Responda às perguntas da seguinte forma:
1 create-next-app@14.2.3 2 Ok to proceed? (y) y 3 ✔ Would you like to use TypeScript? … Yes 4 ✔ Would you like to use ESLint? … Yes 5 ✔ Would you like to use Tailwind CSS? … Yes 6 ✔ Would you like to use `src/` directory? … No 7 ✔ Would you like to use App Router? (recommended) … Yes 8 ✔ Would you like to customize the default import alias (@/*)? … No
Após as instruções, create-next-app criará uma pasta com o nome do projeto e instalará as dependências necessárias.
Navegue dentro do seu projeto e instale as seguintes dependências:
1 cd mongo-auth 2 npm install next-auth bcryptjs mongoose 3 npm install –-save-dev @types/bcryptjs
O pacote próximo-auth fornece funcionalidades de autenticação para aplicativos Next.js. O pacote próximo-autenticação também oferece suporte integrado para vários fornecedores de autenticação e permite fácil integração com seu aplicativo.
A biblioteca bcryptjs fornece funções para hash de senhas usando o algoritmo bcrypt. É comumente usado para armazenar senhas com segurança em bancos de dados, gerando hashes de senha salgados.
Mongoose é uma biblioteca de modelagem de dados de objetos (ODM) para MongoDB e Node.js. Ele fornece uma solução simples e baseada em esquema para modelar os dados do aplicativo, facilitando o trabalho com bancos de dados MongoDB.
Depois de instalar essas dependências, você terá as ferramentas e bibliotecas necessárias para configurar os recursos de autenticação em seu aplicativo Next.js, integrá-lo ao MongoDB, criptografar senhas com segurança e definir modelos de dados usando o Mongoose.
Depois de instalar as dependências, é hora de conectar ao MongoDB. Crie um arquivo
.env.local
na raiz do seu projeto e adicione sua connection string do MongoDB:1 MONGODB_URI=mongodb://127.0.0.1:27017/mydb
Observação: se você estiver se conectando a um cluster do MongoDB Atlas, poderá encontrar sua string de conexão no painel do Atlas.
Crie um arquivo
lib/mongodb.ts
e adicione o seguinte código para conectar a um MongoDB database utilizando o mongoose
:1 import mongoose from "mongoose"; 2 const { MONGODB_URI } = process.env; 3 export const connectDB = async () => { 4 try { 5 const { connection } = await mongoose.connect(MONGODB_URI as string); 6 if (connection.readyState === 1) { 7 return Promise.resolve(true); 8 } 9 } catch (error) { 10 console.error(error); 11 return Promise.reject(error); 12 } 13 };
Estamos importando a biblioteca
mongoose
, que é uma biblioteca ODM Node.js popular para o MongoDB. Em seguida, recuperamos o URI MongoDB das variáveis de ambiente. Em seguida, definimos uma função assíncrona denominada connectDB
que se conecta ao MongoDB database utilizando o URI obtido das variáveis de ambiente.Dentro da função
connectDB
, usamos um blocotry...catch
para lidar com quaisquer erros potenciais que podem ocorrer durante o processo de conexão do banco de dados. Dentro do blocotry
, usamos o mongoose.connect()
para estabelecer uma conexão com o MongoDB database utilizando o URI fornecido. Se a conexão for bem-sucedida, resolvemos a promessa com um valor de true
.Se ocorrer algum erro durante o processo de conexão, ele será capturado pelo bloco
catch
. Nesse caso, registramos o erro no console e rejeitamos a promessa com o erro capturado.Em seguida, crie um arquivo
models/User.ts
e comece adicionando as seguintes importações:1 import mongoose, { Schema, model } from "mongoose";
Adicione a interface do usuário:
1 export interface UserDocument { 2 _id: string; 3 email: string; 4 password: string; 5 name: string; 6 phone: string; 7 image: string; 8 createdAt: Date; 9 updatedAt: Date; 10 }
Adicione o esquema de usuário:
1 const UserSchema = new Schema<UserDocument>({ 2 email: { 3 type: String, 4 unique: true, 5 required: [true, "Email is required"], 6 match: [ 7 /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 8 "Email is invalid", 9 ], 10 }, 11 password: { 12 type: String, 13 required: true 14 }, 15 name: { 16 type: String, 17 required: [true, "Name is required"] 18 } 19 }, 20 { 21 timestamps: true, 22 } 23 );
Isso define um esquema Mongoose para um documento de usuário. O
UserSchema
especifica a estrutura e as regras de validação para dados de usuário armazenados em uma coleção MongoDB.Por fim, exporte o modelo do usuário:
1 const User = mongoose.models?.User || model<UserDocument>('User', UserSchema); 2 export default User;
Isso garante que o modelo de usuário seja definido e esteja disponível para uso no aplicativo, recuperando um modelo existente ou criando um novo com base no esquema especificado.
Primeiro, precisamos de uma chave secreta NextAuth. Esta chave secreta é usada para criptografar tokens JWT e dados de sessão. Você pode obtê-lo simplesmente executando este comando em seu terminal e ele gerará uma chave aleatória para você:
1 npx auth secret
Você precisa copiar o segredo para seu arquivo
.env
:1 AUTH_SECRET=bVNtF3X1k0QwTD9bx5xUUHOFWAwMXwNdvoF9D8AdgtE=
Crie um arquivo
lib/auth.ts
e comece adicionando as seguintes importações:1 import { connectDB } from "@/lib/mongodb"; 2 import User from "@/models/User"; 3 import type { NextAuthOptions } from "next-auth"; 4 import credentials from "next-auth/providers/credentials"; 5 import bcrypt from "bcryptjs";
Defina um objeto NextAuth options da seguinte maneira:
1 export const authOptions: NextAuthOptions = { 2 providers: [ 3 credentials({ 4 name: "Credentials", 5 id: "credentials", 6 credentials: { 7 email: { label: "Email", type: "text" }, 8 password: { label: "Password", type: "password" }, 9 }, 10 async authorize(credentials) {}, 11 }), 12 ], 13 session: { 14 strategy: "jwt", 15 } 16 };
Na função
authorize()
, adicione o seguinte código:1 await connectDB(); 2 const user = await User.findOne({ 3 email: credentials?.email, 4 }).select("+password"); 5 6 if (!user) throw new Error("Wrong Email"); 7 8 const passwordMatch = await bcrypt.compare( 9 credentials!.password, 10 user.password 11 ); 12 13 if (!passwordMatch) throw new Error("Wrong Password"); 14 return user;
Este código verifica as credenciais do usuário (e-mail e senha) em relação às armazenadas em um MongoDB database. Ele garante que as credenciais fornecidas sejam válidas antes de permitir que o usuário prossiga com a autenticação.
Em seguida, crie um
app/api/auth/[...nextauth]/route.ts
arquivo e adicione o seguinte código:1 import { authOptions } from "@/lib/auth"; 2 import NextAuth from "next-auth"; 3 const handler = NextAuth(authOptions); 4 export { handler as GET, handler as POST };
Esse código importa as opções de autenticação de seu módulo, inicializa o manipulador de autenticação usando essas opções e exporta o manipulador a ser usado para solicitações
GET
e POST
nas rotas da API.Em Next.js, uma ação do servidor refere-se a qualquer lógica ou funcionalidade que seja executada no lado do servidor antes de renderizar a página. Isso pode incluir tarefas como buscar dados de uma API externa, acessar um banco de dados ou realizar verificações de autenticação.
Em nosso caso, criaremos uma ação de servidor para registrar usuários. Dentro da raiz do seu projeto, crie um arquivo
actions/register.ts
e comece adicionando as seguintes importações:1 "use server" 2 import { connectDB } from "@/lib/mongodb"; 3 import User from "@/models/User"; 4 import bcrypt from "bcryptjs";
A diretiva "use server", colocada no início do arquivo, instrui Next.js a tratar a(s) função(ões) como uma ação do servidor. Essa função pode ser chamada a partir de componentes do lado do cliente, mas é executada no servidor.
Exporte uma ação de servidor da seguinte maneira:
1 export const register = async (values: any) => { 2 const { email, password, name } = values; 3 4 try { 5 await connectDB(); 6 const userFound = await User.findOne({ email }); 7 if(userFound){ 8 return { 9 error: 'Email already exists!' 10 } 11 } 12 const hashedPassword = await bcrypt.hash(password, 10); 13 const user = new User({ 14 name, 15 email, 16 password: hashedPassword, 17 }); 18 const savedUser = await user.save(); 19 20 }catch(e){ 21 console.log(e); 22 } 23 }
A função
register
lida com o processo de registro de um novo usuário conectando-se ao MongoDB database, verificando os endereços de e-mail existentes, fazendo hash das senhas e salvando os dados do usuário no MongoDB database.Após configurar o NextAuth e implementar uma ação de servidor para registro de usuário, a próxima etapa é criar as páginas de login e registro.
Para começar, vamos nos concentrar na página de login. Crie um arquivo denominado
app/login/page.tsx
e comece importando as dependências necessárias:1 "use client"; 2 import { FormEvent, useState } from "react"; 3 import { signIn } from "next-auth/react"; 4 import { useRouter } from "next/navigation"; 5 import Link from "next/link";
A diretiva
use client
é usada para marcar o componente como código do lado do cliente. Isso significa que o código dentro desse escopo só será executado e estará disponível no ambiente do navegador do usuário, e não no servidor.Defina uma função e exporte-a:
1 export default function Login() {};
No corpo da função, comece adicionando o seguinte código:
1 const [error, setError] = useState(""); 2 const router = useRouter();
Isso estabelece o gerenciamento de estado para mensagens de erro e acesso ao roteador do lado do cliente.
Adicione a função
handleSubmit()
da seguinte forma:1 const handleSubmit = async (event: FormEvent<HTMLFormElement>) => { 2 event.preventDefault(); 3 const formData = new FormData(event.currentTarget); 4 const res = await signIn("credentials", { 5 email: formData.get("email"), 6 password: formData.get("password"), 7 redirect: false, 8 }); 9 if (res?.error) { 10 setError(res.error as string); 11 } 12 if (res?.ok) { 13 return router.push("/"); 14 } 15 };
Isso define uma função assíncrona que lida com o envio de formulários para o login do usuário. Ele evita o comportamento padrão do formulário, extrai credenciais de usuário do formulário usando FormData, tenta se conectar usando NextAuth.js, e lida com o login bem-sucedido redirecionando o usuário para a página inicial e com possíveis erros atualizando o estado de erro do componente.
Retorna o seguinte código JSX:
1 return ( 2 <section className="w-full h-screen flex items-center justify-center"> 3 <form 4 className="p-6 w-full max-w-[400px] flex flex-col justify-between items-center gap-2 5 border border-solid border-black bg-white rounded" 6 onSubmit={handleSubmit}> 7 {error && <div className="text-black">{error}</div>} 8 <h1 className="mb-5 w-full text-2xl font-bold">Sign In</h1> 9 <label className="w-full text-sm">Email</label> 10 <input 11 type="email" 12 placeholder="Email" 13 className="w-full h-8 border border-solid border-black rounded p-2" 14 name="email" /> 15 <label className="w-full text-sm">Password</label> 16 <div className="flex w-full"> 17 <input 18 type="password" 19 placeholder="Password" 20 className="w-full h-8 border border-solid border-black rounded p-2" 21 name="password" /> 22 </div> 23 <button className="w-full border border-solid border-black rounded"> 24 Sign In 25 </button> 26 27 <Link 28 href="/register" 29 className="text-sm text-[#888] transition duration-150 ease hover:text-black"> 30 Don't have an account? 31 </Link> 32 </form> 33 </section> 34 );
Isso simplesmente cria um formulário que permite aos usuários inserir seu e-mail e senha para autenticação e fornece um link para navegar até a página de registro se ainda não tiverem uma conta.
Veja como é o formulário:
Em seguida, vamos criar a página de registro. Crie um arquivo
app/register/page.tsx
e comece adicionando o seguinte código:1 "use client"; 2 import { FormEvent, useRef, useState } from "react"; 3 import { useRouter } from "next/navigation"; 4 import Link from "next/link"; 5 import { register } from "@/actions/register"; 6 7 8 export default function Register() { 9 const [error, setError] = useState<string>(); 10 const router = useRouter(); 11 const ref = useRef<HTMLFormElement>(null); 12 }
Isso cria um componente do lado do cliente que inicializa o estado para tratamento de erros, utiliza o roteador Next.js para navegação e fornece uma referência ao elemento do formulário, que será criado abaixo, usando uma ref.
Adicione a função
handleSubmit()
:1 const handleSubmit = async (formData: FormData) => { 2 const r = await register({ 3 email: formData.get("email"), 4 password: formData.get("password"), 5 name: formData.get("name") 6 }); 7 ref.current?.reset(); 8 if(r?.error){ 9 setError(r.error); 10 return; 11 } else { 12 return router.push("/login"); 13 } 14 };
Isso define uma função assíncrona denominada
handleSubmit
, que é chamada quando um formulário é enviado. A função lida com o envio do formulário, incluindo a extração dos dados do formulário usando FormData, o registro do usuário chamando a ação do servidor de registro, o tratamento de erros e o redirecionamento do usuário para a página de login após o registro bem-sucedido. Caso contrário, ela define o erro que será exibido.Retorna o seguinte código JSX:
1 return( 2 <section className="w-full h-screen flex items-center justify-center"> 3 <form ref = {ref} 4 action={handleSubmit} 5 className="p-6 w-full max-w-[400px] flex flex-col justify-between items-center gap-2 6 border border-solid border-black bg-white rounded"> 7 {error && <div className="">{error}</div>} 8 <h1 className="mb-5 w-full text-2xl font-bold">Register</h1> 9 10 <label className="w-full text-sm">Full Name</label> 11 <input 12 type="text" 13 placeholder="Full Name" 14 className="w-full h-8 border border-solid border-black py-1 px-2.5 rounded text-[13px]" 15 name="name" 16 /> 17 18 <label className="w-full text-sm">Email</label> 19 <input 20 type="email" 21 placeholder="Email" 22 className="w-full h-8 border border-solid border-black py-1 px-2.5 rounded" 23 name="email" 24 /> 25 26 <label className="w-full text-sm">Password</label> 27 <div className="flex w-full"> 28 <input 29 type="password" 30 placeholder="Password" 31 className="w-full h-8 border border-solid border-black py-1 px-2.5 rounded" 32 name="password" 33 /> 34 </div> 35 36 <button className="w-full border border-solid border-black py-1.5 mt-2.5 rounded 37 transition duration-150 ease hover:bg-black"> 38 Sign up 39 </button> 40 41 42 <Link href="/login" className="text-sm text-[#888] transition duration-150 ease hover:text-black"> 43 Already have an account? 44 </Link> 45 </form> 46 </section> 47 )
É assim que parece:
Nesta seção, demonstraremos como usar o
SessionProvider
no layout pai para garantir que o gerenciamento de sessão esteja disponível em todo o aplicativo. Recuperaremos o estado de autenticação da sessão e exibiremos dinamicamente um botão "Sign Out" se o usuário estiver autenticado, ou um botão "Sign In" se o usuário não estiver autenticado.Crie um
app/provider.tsx
arquivo e adicione o seguinte código:1 "use client"; 2 3 import { SessionProvider } from "next-auth/react"; 4 5 type Props = { 6 children?: React.ReactNode; 7 }; 8 9 export const Provider = ({ children }: Props) => { 10 return <SessionProvider>{children}</SessionProvider>; 11 };
Atualize o
app/layout.tsx
com o provedor que criamos. Primeiro importe-o:1 import { Provider } from "./provider";
Em seguida, adicione da seguinte forma:
1 export default function RootLayout({ 2 children, 3 }: Readonly<{ 4 children: React.ReactNode; 5 }>) { 6 return ( 7 <html lang="en"> 8 <Provider> 9 <body className={inter.className}>{children}</body> 10 </Provider> 11 </html> 12 ); 13 }
Atualize o arquivo
app/page.tsx
da seguinte maneira:1 "use client"; 2 import { signOut, useSession } from "next-auth/react"; 3 import Link from "next/link"; 4 import { useRouter } from "next/navigation"; 5 6 export default function Home() { 7 const { status } = useSession(); 8 const router = useRouter(); 9 10 const showSession = () => { 11 if (status === "authenticated") { 12 return ( 13 <button 14 className="border border-solid border-black rounded" 15 onClick={() => { 16 signOut({ redirect: false }).then(() => { 17 router.push("/"); 18 }); 19 20 }} 21 > 22 Sign Out 23 </button> 24 ) 25 } else if (status === "loading") { 26 return ( 27 <span className="text-[#888] text-sm mt-7">Loading...</span> 28 ) 29 } else { 30 return ( 31 <Link 32 href="/login" 33 className="border border-solid border-black rounded" 34 > 35 Sign In 36 </Link> 37 ) 38 } 39 } 40 return ( 41 <main className="flex min-h-screen flex-col items-center justify-center"> 42 <h1 className="text-xl">Home</h1> 43 {showSession()} 44 </main> 45 ); 46 }
Atualize o arquivo
app/globals.css
para remover o CSS personalizado e manter apenas o seguinte:1 @tailwind base; 2 @tailwind components; 3 @tailwind utilities;
Você primeiro precisa se registrar a partir desta interface:
Você pode então fazer login a partir desta interface:
Você será redirecionado para a página inicial com o botão “Sign Out”, que aparece somente quando o usuário está logado, e você pode usá-lo para sair do aplicativo:
Neste tutorial, analisamos as etapas para configurar a autenticação NextAuth.js com o MongoDB como banco de dados de back-end. Começamos criando um novo projeto Next.js e instalando as dependências necessárias. Em seguida, estabelecemos uma conexão com o MongoDB usando o Mongoose e definimos um esquema de usuário para estruturar nossos dados de usuário.
Em seguida, configuramos NextAuth.js gerando uma chave secreta, definindo opções de autenticação e implementando a lógica de autorização para verificar as credenciais do usuário em relação ao MongoDB. Também criamos ações de servidor para o registro de usuários, lidando com operações de banco de dados com segurança no lado do servidor.
Depois de configurar a autenticação, criamos páginas de login e registro usando componentes React e as integramos ao NextAuth.js para autenticação. Lidamos com envios de formulários, tratamento de erros e redirecionamento de usuários com base no status da autenticação.
Por fim, atualizamos o layout para incluir um provedor de sessão para gerenciar sessões de usuário e garantir a exibição adequada do status de autenticação na página inicial.
Ao seguir essas etapas, você pode implementar efetivamente a autenticação em seu aplicativo Next.js usando o NextAuth.js e o MongoDB, fornecendo uma experiência segura e contínua para seus usuários e simplificando o processo de configuração da autenticação para desenvolvedores.
Principais comentários nos fóruns
Akash_Mishra1Akash Mishralast quarter
Atlas_KotkarAtlas_Kotkar2 meses atrás
@Dvir_Ben_Ishay Olá, também estou enfrentando um problema exato dos últimos 7 dias. Não foi possível resolver esse problema. Você já acordou esse problema? Por favor, informe-me ou você pode me escrever em: trocanilkotkar793@mail.com
este código não está funcionando no modo de produção. está mostrando erro
O erro foi causado pela importação de 'mongoose/dist/browser.umd.js