В чем может быть проблема с запросом POST для аутентификации при входе в стек MERN с использованием JWT?

#node.js #express #jwt #express-jwt

Вопрос:

Я создаю приложение для социальных сетей, используя стек MERN.

Я использую POSTMAN для тестирования внутреннего API.

Ниже приведен список зависимостей, т. е. файл package.json.

 {
  "name": "SocialMediaApp",
  "version": "1.0.0",
  "description": "A simple MERN-stack based social media app.",
  "main": "index.js",
  "scripts": {
    "development": "nodemon"
  },
  "author": "Prithvi",
  "license": "MIT",
  "keywords": [
    "react",
    "node",
    "express",
    "mongodb",
    "mern"
  ],
  "dependencies": {
    "@hot-loader/react-dom": "^17.0.1",
    "compression": "^1.7.4",
    "cookie-parser": "^1.4.5",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "express-jwt": "^6.0.0",
    "helmet": "^4.4.1",
    "jshint": "^2.12.0",
    "jsonwebtoken": "^8.5.1",
    "loadash": "^1.0.0",
    "mongodb": "^3.6.4",
    "mongoose": "^5.12.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-hot-loader": "^4.13.0"
  },
  "devDependencies": {
    "@babel/core": "^7.13.10",
    "@babel/preset-env": "^7.13.10",
    "@babel/preset-react": "^7.12.13",
    "babel-loader": "^8.2.2",
    "file-loader": "^6.2.0",
    "nodemon": "^2.0.7",
    "webpack": "^5.24.4",
    "webpack-cli": "^4.5.0",
    "webpack-dev-middleware": "^4.1.0",
    "webpack-hot-middleware": "^2.25.0",
    "webpack-node-externals": "^2.5.2"
  }
} 

На самом деле, когда я пытаюсь протестировать серверный API для запроса POST для входа в конечную http://localhost:3000/auth/signin точку , я получаю ошибку, как показано ниже, с кодом ошибки 401 Unauthorized .

       
{
  "error": "Could not sign in ! :("
}
     

Ниже приведен файл модели пользователя, т. е., user.model.js.

 import mongoose from "mongoose";
import crypto from "crypto";

const UserSchema = new mongoose.Schema({
  name: {
    type: String,
    trim: true,
    required: 'Name is required',
  },
  email: {
    type: String,
    trim: true,
    unique: 'Email already exists',
    match: [/. @. .. /, 'Please fill a valid email address'],
    required: 'Email is required !',
  },
  hashed_password: {
    type: String,
    required: 'Password is required !',
  },
  salt: String,
  updated: Date,
  created: {
    type: Date,
    default: Date.now,
  },
});

/**
 * The password string that's provided by the user is not stored directly in the user
 * document. Instead, it is handled as a virtual field.
 */
UserSchema
  .virtual('password')
  .set(function (password) {
    this._password = password;
    this.salt = this.makeSalt();
    this.hashed_password = this.encryptPassword(password);
  })
  .get(function () {
    return this._password;
  });

/**
 * To add validation constraints to the actual password string that's selected by the end
 * user, we need to add custom validation logic and associate it with the hashed_password
 * field in the schema.
 */
UserSchema.path('hashed_password').validate(function (v) {
  if (this._password amp;amp; this._password.length < 6) {
    this.invalidate('password', 'Password must be atleast 6 characters.');
  }
  if (this.isNew amp;amp; !this._password) {
    this.invalidate('password', 'Password is required.');
  }
}, null);

/**
 * The encryption logic and salt generation logic, which are used to generate the
 * hashed_password and salt values representing the password value, are defined as
 * UserSchema methods.
 */
UserSchema.methods = {
  authenticate: function (plainText) {
    return this.authenticate(plainText) === this.hashed_password;
  },
  encryptPassword: function (password) {
    if (!password) return '';
    try {
      return crypto
        .createHmac('sha1', this.salt)
        .update(password)
        .digest('hex');
    } catch (err) {
      return '';
    }
  },
  makeSalt: function () {
    return Math.round(new Date().valueOf() * Math.random())   '';
  },
};

export default mongoose.model('User', UserSchema); 

Below is the controller file i.e., auth.controller.js.

 import User from "../models/user.model";
import jwt from 'jsonwebtoken';
import expressJwt from "express-jwt";
import config from "./../../config/config";

const signin = async (req, res) => {
    try {
        let user = await User.findOne({
            "email": req.body.email
        });
        if (!user) {
            return res.status('401').json({
                error: "User not found"
            });
        }

        if (!user.authenticate(req.body.password)) {
            return res.status('401').send({
                error: "Email and passwords don't match."
            });
        }

        const token = jwt.sign({
            _id: user._id,
        }, config.jwtSecret);

        res.cookie('t', token, {
            expire: new Date()   9999
        });

        return res.json({
            token,
            user: {
                _id: user._id,
                name: user.name,
                email: user.email
            }
        });
    } catch (err) {
        return res.status('401').json({
            error: "Could not sign in ! :("
        });
    }
};

const signout = (req, res) => {
    res.clearCookie("t");
    return res.status('200').json({
        message: "signed out"
    });
};

const requireSignin = expressJwt({
    secret: config.jwtSecret,
    userProperty: 'auth',
    algorithms: ['HS256']
});

const hasAuthorization = (req, res, next) => {
    const authorized = req.profile amp;amp; req.auth amp;amp; req.profile._id == req.auth._id;
    if (!(authorized)) {
        return res.status('403').json({
            error: "User isn't authorized !"
        });
    }
    next();
};

export default {
    signin,
    signout,
    requireSignin,
    hasAuthorization
} 

And here is the routes fle i.e., auth.routes.js.

 import express from "express";
import authCtrl from "../controllers/auth.controller";

const router = express.Router();

router.route('/auth/signin')
    .post(authCtrl.signin);
router.route('/auth/signout')
    .get(authCtrl.signout);

export default router; 

Lastly, express.js file.

 import express from "express";
import path from "path";
import cookieParser from "cookie-parser";
import compress from "compression";
import cors from "helmet";
import helmet from "cors";
import Template from "./../template";
import userRoutes from "./routes/user.routes";
import authRoutes from "./routes/auth.routes";

const CURRENT_WORKING_DIR = process.cwd();
const app = express();

app.use(express.json());
app.use(express.urlencoded({
  extended: true
}));
app.use(cookieParser());
app.use(compress());
app.use(helmet());
app.use(cors());

app.use('/dist', express.static(path.join(CURRENT_WORKING_DIR, 'dist')));

app.use('/', userRoutes);
app.use('/', authRoutes);

app
  .get("/", (req, res) => {
    res.status(200).send(Template());
  });

app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    res.status(401).json({
      "error": err.name   ": "   err.message
    });
  } else if (err) {
    res.status(400).json({
      "error": err.name   ": "   err.message
    });
    console.log(err);
  }
});

export default app; 

P. S

Я получаю следующую ошибку в терминале для user.model.js.

 RangeError: Maximum call stack size exceeded
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17) 

Комментарии:

1. Не могли бы вы поделиться тем, как вы называете эту услугу от почтальона? Я хочу показать, как вы передаете параметр и все такое.

2. Отправив запрос на конечную точку api, т. е. http://localhost:3000/auth/signin указав адрес электронной почты и пароль вместе с запросом POST .

3. Не могли бы вы, пожалуйста, прикрепить экран для того же самого?

4. Я тебя не понял ! Не могли бы вы быть более конкретными ?

5. Не могли бы вы изменить эту часть return res.status('401').json({ error: "Could not sign in ! :(" }); кода на return res.status('401').json({ error: err.message }); и снова выполнить запрос, чтобы мы могли лучше видеть, что происходит в контроллере.

Ответ №1:

const token = jwt.sign({ _id: user.id, }, config.jwtSecret);

здесь вы должны использовать _id: user._id

Комментарии:

1. Я все еще получаю ту же ошибку. P. S — Вы можете проверить детали вопроса, которые я обновил, добавив express.js файл.

Ответ №2:

Я думаю, что вы пытаетесь сделать что-то вроде этого: в методе аутентификации просто вызовите encryptPassword() , а authenticate() не повторите. Вы вызывали authenticate() в рекурсивном цикле без контроля; это было причиной ошибки.

 UserSchema.methods = {
  authenticate: function (plainText) {
    return this.encryptPassword(plainText) === this.hashed_password;
  },
  encryptPassword: function (password) {
    if (!password) return '';
    try {
      return crypto
        .createHmac('sha1', this.salt)
        .update(password)
        .digest('hex');
    } catch (err) {
      return '';
    }
  },
  makeSalt: function () {
    return Math.round(new Date().valueOf() * Math.random())   '';
  },
};
 

Ответ №3:

Я не знаю почему, но я заинтригован import cors from 'helmet' и import helmet from 'cors'

Изменить: Вы пытались вернуться true в:

authenticate: function (plainText) { return this.authenticate(plainText) === this.hashed_password; }

Если ошибки больше нет, то вы возвращаетесь из рекурсивного цикла authenticate .

Комментарии:

1. после того, как вы указали, даже я удивлен. Довольно забавно, я не заметил этой ошибки !! Я думаю, это типографская ошибка.

2. вы можете узнать подробности вопроса о проверке, которые я обновил, для получения дополнительной информации, связанной с ошибкой, которую я все еще показываю после исправления ошибок импорта.