api - 使用 Passport Local 登录向 POST 请求返回 400 错误(使用 Axios、MERN 和 React Hooks)
问题描述
我正在使用 Passport Local 对我的应用程序进行身份验证。这是我使用 React Hooks 完成的第一个项目。auth 策略是我之前在 React 中使用类组件时使用的策略。所以我很好奇我需要考虑的方法是否有所不同。
目前,当我在“登录”组件的表单中提交“电子邮件”和“密码”数据时,我收到对我的 POST 请求的 400 错误响应以及“错误凭据”的相应消息(此消息只是对400 JSON 响应):
xhr.js:177 POST http://localhost:5005/api/auth/login 400 (Bad Request)
但我使用的是从数据库复制的确切输入。我相信这意味着我发送的要与记录匹配的数据(在这种情况下是电子邮件)没有被正确读取。
为了确保我的应用程序访问数据库中的用户没有问题,我创建了一个单独的 API 请求来返回所有用户。这确实有效。所以在我看来,我发送到数据库以进行验证的数据存在一些问题。
你能看出我在哪里犯了错误吗?
客户端/src/components/Login.js
import {React, useState} from 'react'
import { Link } from 'react-router-dom'
import { allUsers, login } from '../services/auth'
// console.log('login loading')
function useInput(initialValue){
const [value, setValue] = useState(initialValue);
function handleChange(event){
setValue(event.target.value);
}
return [value,handleChange]
}
export default function Login(props) {
const [email, setEmail] = useInput('');
const [password, setPassword] = useInput('');
const [message, setMessage] = useState('');
const [user, setUser] = useState('')
const handleSubmit = event => {
event.preventDefault();
console.log('email:', email, 'password:', password)
// allUsers().then(data => {
// console.log(data)
// })
login(email, password).then(data => {
if (data.message) {
// console.log(data.message);
setMessage(data.message);
} else {
setUser(data)
console.log(user);
props.history.push('/dashboard');
}
});
};
// console.log('login function loading')
return (
<div>
<h1>Login.js</h1>
<div class='loginForm'>
<form onSubmit={handleSubmit}>
<label>Email</label>
<input
type='text'
name='email'
value={email}
onChange={setEmail}
id='email'
/>
<label>Password</label>
<input
type='password'
name='password'
value={password}
onChange={setPassword}
id='password'
/>
{message && (
<alert variant='danger'>{message}</alert>
)}
<button type='submit'>Login</button>
</form>
</div>
<Link to="/auth/google">Login With Google</Link>
</div>
)
}
客户端/src/services/auth.js
import axios from 'axios';
const allUsers = () => {
return axios
.get('/api/auth/users')
.then(response => {
return response.data;
})
.catch(err => {
console.log(err.response.data)
})
}
const signup = (email, password, firstname, lastname) => {
return axios
.post('/api/auth/signup', { email, password, firstname, lastname})
.then(response => {
// console.log(response)
return response.data;
})
.catch(err => {
// console.log(err)
return err.response.data;
});
};
const login = (email, password) => {
return axios
.post('/api/auth/login', { email, password })
.then(response => {
return response.data;
})
.catch(err => {
return err.response.data;
});
};
const logout = () => {
return axios
.delete('/api/auth/logout')
.then(response => {
return response.data;
})
.catch(err => {
return err.response.data;
});
};
export { signup, login, logout, allUsers };
路线/auth-routes.js
const express = require('express');
const passport = require('passport');
const bcrypt = require('bcrypt');
const User = require('../models/User.model');
const router = express.Router();
// Get all users
router.get('/users', (req, res) => {
User.find().then((users) => {
console.log(users);
res.status(200).json(users);
});
});
// Signup route
router.post('/signup', (req, res) => {
const {
email, password, firstname, lastname,
} = req.body;
if (!password || password.length < 8) {
return res
.status(400)
.json({ message: 'Your password must be 8 char. min.' });
}
if (!email) {
return res
.status(400)
.json({ message: 'Your email cannot be empty' });
}
// check if email exists in database -> show message
User.findOne({ email })
.then((found) => {
if (found) {
return res
.status(400)
.json({ message: 'This email is already taken' });
}
// hash the password, create the user and send the user to the client
const salt = bcrypt.genSaltSync();
const hash = bcrypt.hashSync(password, salt);
return User.create({
email, password: hash, firstname, lastname,
}).then(
(dbUser) => {
// login with passport:
req.login(dbUser, (err) => {
if (err) {
return res
.status(500)
.json({ message: 'Error while attempting to login' });
}
return res.status(200).json(dbUser);
});
},
);
})
.catch((err) => {
res.json(err);
});
});
// Login route
router.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user) => {
if (err) {
return res.status(500).json({ message: 'Error while authenticating' });
}
if (!user) {
return res.status(400).json({ message: 'Wrong credentials' });
}
req.login(user, (error) => {
if (error) {
return res
.status(500)
.json({ message: 'Error while attempting to login' });
}
return res.status(200).json(user);
});
})(req, res, next);
});
// Delete user route
router.delete('/logout', (req, res) => {
req.logout();
res.json({ message: 'Successful logout' });
});
// returns the logged in user
router.get('/loggedin', (req, res) => {
res.json(req.user);
});
// when login is successful, retrieve user info
router.get('/login/success', (req, res) => {
if (req.user) {
res.json({
success: true,
message: 'user has successfully authenticated',
user: req.user,
cookies: req.cookies,
});
}
});
// auth with google
router.get(
'/google',
passport.authenticate('google', {
scope: [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
],
}),
);
router.get(
'/google/callback',
passport.authenticate('google', {
successRedirect: '/private-page',
// here you would redirect to the login page using traditional login approach
failureRedirect: '/login',
}),
);
module.exports = router;
应用程序.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const bcrypt = require('bcrypt');
const cors = require('cors');
// ℹ️ Gets access to environment variables/settings
// https://www.npmjs.com/package/dotenv
require('dotenv/config');
// ℹ️ Connects to the database
require('./db');
// Handles http requests (express is node js framework)
// https://www.npmjs.com/package/express
const express = require('express');
const app = express();
// ℹ️ This function is getting exported from the config folder. It runs most middlewares
require('./config')(app);
app.use(
cors({
// this could be multiple domains/origins, but we will allow just our React app
origin: ['http://localhost:3000'],
}),
);
// session configuration
const session = require('express-session');
// session store using mongo
const MongoStore = require('connect-mongo')(session);
const mongoose = require('./db/index');
app.use(
session({
secret: process.env.SESSION_SECRET,
cookie: { maxAge: 1000 * 60 * 60 * 24 },
saveUninitialized: false,
// Forces the session to be saved back to the session store,
// even if the session was never modified during the request.
resave: true,
store: new MongoStore({
mongooseConnection: mongoose.connection,
url: 'mongodb://localhost:27017',
}),
}),
);
// end of session configuration
// passport configuration
const User = require('./models/User.model');
// we serialize only the `_id` field of the user to keep the information stored minimum
passport.serializeUser((user, done) => {
// eslint-disable-next-line no-underscore-dangle
done(null, user._id);
});
// when we need the information for the user, the deserializeUser function is called with
// the id that we previously serialized to fetch the user from the database
passport.deserializeUser((id, done) => {
User.findById(id)
.then((dbUser) => {
done(null, dbUser);
})
.catch((err) => {
done(err);
});
});
passport.use(
// new GoogleStrategy(
// {
// clientID: process.env.GOOGLE_CLIENTID,
// clientSecret: process.env.GOOGLE_CLIENTSECRET,
// callbackURL: '/auth/google/callback',
// },
// (accessToken, refreshToken, profile, done) => {
// // to see the structure of the data in received response:
// console.log('Google account details:', profile);
// User.findOne({ googleID: profile.id })
// .then((user) => {
// if (user) {
// done(null, user);
// return;
// }
// User.create({ googleID: profile.id })
// .then((newUser) => {
// done(null, newUser);
// })
// .catch((err) => done(err)); // closes User.create()
// })
// .catch((err) => done(err)); // closes User.findOne()
// },
// ),
new LocalStrategy((email, password, done) => {
// login
User.findOne({ email: email })
.then((userFromDB) => {
if (userFromDB === null) {
// there is no user with this email
done(null, false, { message: 'This email does not exist in the database' });
} else if (!bcrypt.compareSync(password, userFromDB.password)) {
// the password is not matching
done(null, false, { message: 'Wrong password' });
} else {
// the userFromDB should now be logged in
done(null, userFromDB);
}
})
.catch((err) => {
console.log(err);
});
}),
);
app.use(passport.initialize());
app.use(passport.session());
// end of passport
// Start handling routes here
// Contrary to the views version, all routes are controled from the routes/index.js
const index = require('./routes');
app.use('/api', index);
const auth = require('./routes/auth-routes');
app.use('/api/auth', auth);
// Allows access to the API from different domains/origins BEFORE session
app.use(
cors({
// this could be multiple domains/origins, but we will allow just our React app
origin: ['http://localhost:3000'],
}),
);
// Start handling routes here
// Contrary to the views version, all routes are controled from the routes/index.js
// This could be a conflict with line 104, so I commented it out. We can reinstate
// const allRoutes = require('./routes');
// app.use('/api', allRoutes);
const admin = require('./routes/admin');
app.use('/api', admin);
// ❗ To handle errors. Routes that don't exist or errors that you handle in specific routes
require('./error-handling')(app);
module.exports = app;
解决方案
在查看 Passport docs 部分后,我找到了解决此问题的方法,该部分讨论了输入本地策略的参数:
http://www.passportjs.org/docs/username-password/
我也应该阅读 vesse 对这个问题的解决方案,因为它也提供了相同的解决方案:
Passport local 返回错误 400 与邮递员的错误请求
本质上,正如 Passport 文档所说:
默认情况下,LocalStrategy 期望在名为 username 和 password 的参数中找到凭据。如果您的站点希望以不同的方式命名这些字段,则可以使用选项来更改默认值。
问题是我已将输入新 LocalStrategy 的参数更改为“电子邮件”和“密码”,而 Passport 只接受“用户名”和“密码”。您可以将电子邮件用作参数之一,但必须将其定义为用户名。
我添加了以下内容作为新 LocalStrategy 的第一个参数:
{
usernameField: 'email',
passwordField: 'password',
}
我的 app.js 中完整的更正代码:
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
(email, password, done) => {
// login
User.findOne({ email })
.then((userFromDB) => {
if (userFromDB === null) {
// there is no user with this email
done(null, false, { message: 'This email does not exist in the database' });
} else if (!bcrypt.compareSync(password, userFromDB.password)) {
// the password is not matching
done(null, false, { message: 'Wrong password' });
} else {
// the userFromDB should now be logged in
done(null, userFromDB);
}
})
.catch((err) => {
console.log(err);
});
},
),
);
推荐阅读
- python - heroku 在 python 中打开应用程序到系统
- c# - Unity C# 变量在 OnCollisionEnter 中不起作用
- visual-studio-code - 如何克服 vs 代码拒绝更改为我选择的字体的问题?
- html - CSS网格的网格模板区域将所有内容移动到右上角
- db2 - db2 - 在不同的表上使用 select 更新批量行
- php - 在 WooCommerce 中将特定选项值显示为选择字段的默认值
- android - 如何将 RenderScript 的分配表面与 MediaCodec 一起使用?
- swift - 如何在视图之间传递@State var?
- flutter - Flutter 中的紧约束与松约束有什么区别
- node.js - 根据一个数据属性对获取的数据进行排序并将其存储在单独的对象中