Отправка ответа JSON при сбое аутентификации Passport.js
Я использую Node.js
в качестве бэкэнд-сервера API для клиента iPhone. Я использую Passport.js
для аутентификации с local strategy
. Соответствующий код ниже:
// This is in user.js, my user model
UserSchema.static('authenticate', function(username, password, callback) {
this.findOne({ username: username }, function(err, user) {
if (err){
console.log('findOne error occurred');
return callback(err);
}
if (!user){
return callback(null, false);
}
user.verifyPassword(password, function(err, passwordCorrect){
if (err){
console.log('verifyPassword error occurred');
return callback(err);
}
if (!passwordCorrect){
console.log('Wrong password');
return callback(err, false);
}
console.log('User Found, returning user');
return callback(null, user);
});
});
});
а также
// This is in app.js
app.get('/loginfail', function(req, res){
res.json(403, {message: 'Invalid username/password'});
});
app.post('/login',
passport.authenticate('local', { failureRedirect: '/loginfail', failureFlash: false }),
function(req, res) {
res.redirect('/');
});
Прямо сейчас мне удалось перенаправить неудачный вход в систему в /loginfail, где я отправил обратно часть JSON клиенту iPhone. Тем не менее, это не имеет достаточной детализации. Я хочу иметь возможность отправлять соответствующие ошибки клиенту iPhone, например: "Пользователь не найден" или "Пароль неверный". С моим существующим кодом я не вижу, как это можно сделать.
Я пытался следовать примерам для пользовательского обратного вызова на сайте passport.js, но я просто не могу заставить его работать из-за отсутствия понимания узла. Как я могу изменить свой код, чтобы иметь возможность отправить файл res.json с соответствующим кодом ошибки/сообщением?
РЕДАКТИРОВАТЬ: я пытаюсь что-то вроде этого сейчас:
// In app.js
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
console.log(info);
// *** Display message without using flash option
// re-render the login form with a message
return res.redirect('/login');
}
console.log('got user');
return res.json(200, {user_id: user._id});
})(req, res, next);
});
// In user.js
UserSchema.static('authenticate', function(username, password, callback) {
this.findOne({ username: username }, function(err, user) {
if (err){
console.log('findOne error occurred');
return callback(err);
}
if (!user){
return callback(null, false);
}
user.verifyPassword(password, function(err, passwordCorrect){
if (err){
return callback(err);
}
if (!passwordCorrect){
return callback(err, false, {message: 'bad password'});
}
console.log('User Found, returning user');
return callback(null, user);
});
});
});
Но вернемся, когда я пытаюсь открыть console.log(info), он просто говорит неопределенный. Я не знаю, как заставить этот пользовательский обратный вызов работать... Любая помощь будет оценена!
Ответы
Ответ 1
Я считаю, что функция обратного вызова, которую ваши "аутентифицирующие" статические вызовы (называемые "обратным вызовом" в вашем коде) принимает третий параметр - "информация", который может предоставить ваш код. Затем вместо передачи в объект {failRedirect:...} передайте функцию, которая принимает 3 аргумента - err, user и info. "Информация", предоставленная вами в вашем методе проверки подлинности, будет передана этому обратному вызову.
Паспорт вызывает этот сценарий "пользовательский обратный вызов". Смотрите документы здесь:
http://passportjs.org/guide/authenticate/
Ответ 2
У меня была похожая проблема с Passport
и не удалось войти в систему. Я строил API и хотел, чтобы все ответы возвращались в JSON
. Паспорт отвечает на неверный пароль со статусом: 401
и тело: не Unauthorized
. Это просто текстовая строка в теле, а не JSON, поэтому он сломал мой клиент, который ожидал все JSON.
Оказывается, есть способ заставить Passport просто возвращать ошибку в фреймворк, а не пытаться отправлять ответ сам.
Ответ заключается в том, чтобы установить failWithError
в параметрах, передаваемых для аутентификации: https://github.com/jaredhanson/passport/issues/126#issuecomment-32333163
Из комментария Джаредхансона в номере:
app.post('/login',
passport.authenticate('local', { failWithError: true }),
function(req, res, next) {
// handle success
if (req.xhr) { return res.json({ id: req.user.id }); }
return res.redirect('/');
},
function(err, req, res, next) {
// handle error
if (req.xhr) { return res.json(err); }
return res.redirect('/login');
}
);
Это вызовет обработчик ошибок после next(err)
вызова Passport next(err)
. Для моего приложения я написал общий обработчик ошибок, специфичный для моего сценария использования: просто указать ошибку JSON:
// Middleware error handler for json response
function handleError(err,req,res,next){
var output = {
error: {
name: err.name,
message: err.message,
text: err.toString()
}
};
var statusCode = err.status || 500;
res.status(statusCode).json(output);
}
Затем я использовал его для всех маршрутов API:
var api = express.Router();
...
//set up some routes here, attached to api
...
// error handling middleware last
api.use( [
handleError
] );
Я не нашел опцию failWithError
в документации. Я наткнулся на него во время трассировки кода в отладчике.
Кроме того, прежде чем я понял это, я попробовал "пользовательский обратный вызов", упомянутый в ответе @Kevin_Dente, но у меня это не сработало. Я не уверен, было ли это для более старой версии Passport или я просто делал это неправильно.
Ответ 3
Существует официальная документация для Пользовательского обратного вызова:
app.get('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
https://github.com/passport/www.passportjs.org/blob/master/views/docs/authenticate.md
Ответ 4
Вы можете сделать это без пользовательских обратных вызовов, используя свойство passReqToCallback
в определении стратегии:
passport.use(new LocalStrategy({passReqToCallback: true}, validateUserPassword));
Затем вы можете добавить свой собственный код ошибки аутентификации к запросу в коде вашей стратегии:
var validateUserPassword = function (req, username, password, done) {
userService.findUser(username)
.then(user => {
if (!user) {
req.authError = "UserNotFound";
return done(null, false);
}
И, наконец, вы можете обработать эти пользовательские ошибки в вашем маршруте:
app.post('/login', passport.authenticate('local', { failWithError: true })
function (req, res) {
....
}, function(err, req, res, next) {
if(req.autherror) {
res.status(401).send(req.autherror)
} else {
....
}
}
);
Ответ 5
Согласно официальной документации Passport
вы можете использовать пользовательскую функцию обратного вызова для обработки случая неудачной авторизации и переопределения сообщения по умолчанию.
Если вы разрабатываете REST API и хотите отправить ответ в формате JSON, как показано ниже:
{
"error": {
"name": "JsonWebTokenError",
"message": "invalid signature"
},
"message": "You are not authorized to access this protected resource",
"statusCode": 401,
"data": [],
"success": false
}
Я использовал Passport JWT
аутентификацию для защиты некоторых моих маршрутов и применил authMiddleware
как authMiddleware
ниже:
Приложение/промежуточное программное /authMiddleware.js
const express = require('express');
const router = express.Router();
const passport = require('passport');
const _ = require('lodash');
router.all('*', function (req, res, next) {
passport.authenticate('local', function(err, user, info) {
// If authentication failed, 'user' will be set to false. If an exception occurred, 'err' will be set.
if (err || !user || _.isEmpty(user)) {
// PASS THE ERROR OBJECT TO THE NEXT ROUTE i.e THE APP COMMON ERROR HANDLING MIDDLEWARE
return next(info);
} else {
return next();
}
})(req, res, next);
});
module.exports = router;
приложение/маршруты/approutes.js
const authMiddleware = require('../middlewares/authMiddleware');
module.exports = function (app) {
// secure the route by applying authentication middleware
app.use('/users', authMiddleware);
.....
...
..
// ERROR-HANDLING MIDDLEWARE FOR SENDING ERROR RESPONSES TO MAINTAIN A CONSISTENT FORMAT
app.use((err, req, res, next) => {
let responseStatusCode = 500;
let responseObj = {
success: false,
data: [],
error: err,
message: 'There was some internal server error',
};
// IF THERE WAS SOME ERROR THROWN BY PREVIOUS REQUEST
if (!_.isNil(err)) {
// IF THE ERROR IS REALTED TO JWT AUTHENTICATE, SET STATUS CODE TO 401 AND SET A CUSTOM MESSAGE FOR UNAUTHORIZED
if (err.name === 'JsonWebTokenError') {
responseStatusCode = 401;
responseObj.message = 'You are not authorized to access this protected resource';
}
}
if (!res.headersSent) {
res.status(responseStatusCode).json(responseObj);
}
});
};