首页 > 解决方案 > 使用 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;

标签: apiauthenticationaxiosreact-hookspassport.js

解决方案


在查看 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);
        });
    },
  ),

);

推荐阅读