使用 Next-Auth 和 MongoDB 进行凭据身份验证 - 第 1 部分
身份验证有时会比较复杂,因为我们需要考虑很多因素,例如会话管理、保护多个路由/页面、哈希密码、注册和登录时验证用户凭证等。此外,从头创建身份验证可能非常繁琐。
如果您正在使用 Next.JS,那么您应该尝试使用Next-Auth,因为它提供了许多身份验证方案,如 JWT、cookie 等。并且还可以使用第三方身份验证提供商,如 Google、Facebook,甚至 Discord(是的!)。
此外,next-auth 有助于会话管理,以便服务器不容易被欺骗。
除了提供商之外,我们还将研究根据用户凭证(如电子邮件和密码)设置身份验证。
身份验证期间需要考虑的事项
- 客户端表单验证
- 服务器表单值验证
- 出于显而易见的原因,在注册时对用户密码进行哈希处理
- 存储到数据库中
- 登录时检查哈希密码
- 保护未经身份验证的用户的路由
- 前端和后端的正确错误处理
我们需要的包
我使用 Next.js 作为演示的框架。
与此同时
- next-auth用于身份验证
- bycryptjs用于对密码进行哈希处理
- MongoDB函数
笔记
这不是前端教程,所以我不会介绍任何有关成功事件和/或 CSS 内容的通知。
网站脚手架
该网站非常简单,由 4 个页面组成,显然还有一个导航栏,以便更好地演示:
- 主页
- 登录/注册页面
- 修改密码页面
安装包并设置数据库
npm i next-auth mongodb bcryptjs
在安装过程中,我们将在他们的网站上注册一个免费的 MongoDB 帐户。
.env.local
现在,我们可以使用他们仪表板上的连接代码连接到该数据库。为了获得更精细、更安全的代码,我们应该使用文件内部的 MongoURL 。
注册路线
在登录之前,用户需要注册特定网站。NextJS 允许我们pages/api
使用 NodeJS 环境在文件夹中编写 API 代码。它也将遵循相同的文件夹结构路由。
对于注册路由,我们将创建一个路由pages/api/auth/signup.js
。我们还需要确保只接受POST方法,不接受其他任何方法。
注册路线中要做的事情
- 获取用户凭证
- 证实
- 发送错误代码(如果有)
- 连接到数据库
- 检查是否有任何现有用户具有相同的电子邮件地址
- 使用 bycrypt js 对密码进行哈希处理
bycrypt js在密码散列期间返回一个Promise ,因此我们需要等待响应。
password: await hash(password, 12)
//hash(plain text, no. of salting rounds)
- 如果一切顺利,则发送响应并关闭与数据库的连接
import { MongoClient } from 'mongodb';
import { hash } from 'bcryptjs';
async function handler(req, res) {
//Only POST mothod is accepted
if (req.method === 'POST') {
//Getting email and password from body
const { email, password } = req.body;
//Validate
if (!email || !email.includes('@') || !password) {
res.status(422).json({ message: 'Invalid Data' });
return;
}
//Connect with database
const client = await MongoClient.connect(
`mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@${process.env.MONGO_CLUSTER}.n4tnm.mongodb.net/${process.env.MONGO_DB}?retryWrites=true&w=majority`,
{ useNewUrlParser: true, useUnifiedTopology: true }
);
const db = client.db();
//Check existing
const checkExisting = await db
.collection('users')
.findOne({ email: email });
//Send error response if duplicate user is found
if (checkExisting) {
res.status(422).json({ message: 'User already exists' });
client.close();
return;
}
//Hash password
const status = await db.collection('users').insertOne({
email,
password: await hash(password, 12),
});
//Send success response
res.status(201).json({ message: 'User created', ...status });
//Close DB connection
client.close();
} else {
//Response for other than POST method
res.status(500).json({ message: 'Route not valid' });
}
}
export default handler;
现在我们的注册路线已经到位,是时候将前端连接到后端了。
发布注册表
import { signIn } from 'next-auth/client';
//...
const onFormSubmit = async (e) => {
e.preventDefault();
//Getting value from useRef()
const email = emailRef.current.value;
const password = passwordRef.current.value;
//Validation
if (!email || !email.includes('@') || !password) {
alert('Invalid details');
return;
}
//POST form values
const res = await fetch('/api/auth/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
password: password,
}),
});
//Await for data for any desirable next steps
const data = await res.json();
console.log(data);
};
//...
有了注册登录后,我们来处理登录逻辑。
使用 Next-Auth 登录
Next-Auth 为我们提供了客户端 API以及REST API
NextAuth.js 客户端库可以轻松地与 React 应用程序的会话进行交互。
NextAuth.js 公开了一个由 NextAuth.js 客户端使用的REST API 。
我们将使用两者来登录用户。
要将 NextAuth.js 添加到项目中,请创建一个名为 的
[...nextauth].js
文件pages/api/auth
。所有请求
/api/auth/*
(登录、回调、退出等)将自动由 NextAuth.js 处理。
在 next-auth 的帮助下,我们需要实现自己的登录逻辑来检查数据库中存储的用户。
签到路线中要做的事情:
- 配置使用 JWT
- 从下一个身份验证指定提供商(凭证)
如需更多提供商,请查看
- 连接到数据库
- 检查用户是否存在
- 发送错误响应(如果有)
- 将散列密码与存储在数据库中的密码进行比较
- 发送回复
- 关闭数据库连接
在[...nextauth].js
:
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import { MongoClient } from 'mongodb';
import { compare } from 'bcryptjs';
export default NextAuth({
//Configure JWT
session: {
jwt: true,
},
//Specify Provider
providers: [
Providers.Credentials({
async authorize(credentials) {
//Connect to DB
const client = await MongoClient.connect(
`mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@${process.env.MONGO_CLUSTER}.n4tnm.mongodb.net/${process.env.MONGO_DB}?retryWrites=true&w=majority`,
{ useNewUrlParser: true, useUnifiedTopology: true }
);
//Get all the users
const users = await client.db().collection('users');
//Find user with the email
const result = await users.findOne({
email: credentials.email,
});
//Not found - send error res
if (!result) {
client.close();
throw new Error('No user found with the email');
}
//Check hased password with DB password
const checkPassword = await compare(credentials.passowrd, result.passowrd);
//Incorrect password - send response
if (!checkPassword) {
client.close();
throw new Error('Password doesnt match');
}
//Else send success response
client.close();
return { email: result.email };
},
}),
],
});