С паспорт :
Единственной целью Passport является аутентификация запросов, что он делает с помощью расширяемого набора плагинов, известных как стратегии.
Кроме того от локальный паспорт :
Стратегия локальной аутентификации аутентифицирует пользователей, используя имя пользователя и пароль. Стратегия требует обратного вызова проверки, который принимает эти учетные данные и звонки, выполненные с предоставлением пользователю.
Таким образом, локальная стратегия паспорта будет аутентифицировать только запросы, сделанные как с username
, так и с password
. Следовательно, клиент должен будет отправлять эти учетные данные каждый раз, когда он хочет получить доступ к приложению.
Итак, чтобы аутентифицировать запрос с помощью веб-токенов, вам необходимо предоставить процесс входа в систему, который устанавливает JWT. (Затем клиент должен будет отправить только токен, не сохраняя и не передавая пароль каждый раз)
Для этого на npm доступно много пакетов. Я лично использую и рекомендую jsonwebtoken
Модель пользователя
Предполагается, что у вас есть пользовательская модель, такая как
const UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
maxlength: 254,
trim: true,
match: /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/
// Ref : RFC 5322 compliant regex
// Visualizer : https://regexper.com/#(%3F%3A%5Ba-z0-9!%23%24%25%26'*%2B%2F%3D%3F%5E_%60%7B%7C%7D~-%5D%2B(%3F%3A%5C.%5Ba-z0-9!%23%24%25%26'*%2B%2F%3D%3F%5E_%60%7B%7C%7D~-%5D%2B)*%7C%22(%3F%3A%5B%5Cx01-%5Cx08%5Cx0b%5Cx0c%5Cx0e-%5Cx1f%5Cx21%5Cx23-%5Cx5b%5Cx5d-%5Cx7f%5D%7C%5C%5C%5B%5Cx01-%5Cx09%5Cx0b%5Cx0c%5Cx0e-%5Cx7f%5D)*%22)%40(%3F%3A(%3F%3A%5Ba-z0-9%5D(%3F%3A%5Ba-z0-9-%5D*%5Ba-z0-9%5D)%3F%5C.)%2B%5Ba-z0-9%5D(%3F%3A%5Ba-z0-9-%5D*%5Ba-z0-9%5D)%3F%7C%5C%5B(%3F%3A(%3F%3A(2(5%5B0-5%5D%7C%5B0-4%5D%5B0-9%5D)%7C1%5B0-9%5D%5B0-9%5D%7C%5B1-9%5D%3F%5B0-9%5D))%5C.)%7B3%7D(%3F%3A(2(5%5B0-5%5D%7C%5B0-4%5D%5B0-9%5D)%7C1%5B0-9%5D%5B0-9%5D%7C%5B1-9%5D%3F%5B0-9%5D)%7C%5Ba-z0-9-%5D*%5Ba-z0-9%5D%3A(%3F%3A%5B%5Cx01-%5Cx08%5Cx0b%5Cx0c%5Cx0e-%5Cx1f%5Cx21-%5Cx5a%5Cx53-%5Cx7f%5D%7C%5C%5C%5B%5Cx01-%5Cx09%5Cx0b%5Cx0c%5Cx0e-%5Cx7f%5D)%2B)%5C%5D)
// SFSM : https://en.wikipedia.org/wiki/Finite-state_machine
// Xcrowzz' note : Seems to work for 99.99% of email addresses, containing either local, DNS zone and/or IPv4/v6 addresses.
// Xcrowzz' note : Regex can be targeted for Catastrophic Backtracking (https://www.regular-expressions.info/catastrophic.html) ; See https://www.npmjs.com/package/vuln-regex-detector to check them before someone DDOS your app.
},
password: {
type: String,
required: true,
minlength: 8,
maxlength: 254,
select: false
},
username: {
type: String,
required: false,
unique: true,
maxlength: 254
}
});
module.exports = mongoose.model('User', UserSchema);
Пользовательский контроллер
Теперь пришло время реализовать генерацию и подпись JWT, что должно быть сделано, когда пользователь успешно вошел в систему. Ака, когда оба значения username
и password
, указанные в запросе тела, соответствуют БД (Не не забудьте сравнить открытый пароль с хешем, сохраненным с помощью bcrypt.compare
)
const jwt = require('jsonwebtoken');
[...]
exports.logUser = async (req, res) => {
if (!req.body.email || !req.body.password) { return res.status(400).send('Bad Request');}
else {
await UserModel.findOne({ email: req.body.email }, async (err, user) => {
if (err) return res.status(500).send('Internal Server Error');
if (!user) return res.status(404).send('Not Found');
let passwordIsValid = await bcrypt.compare(req.body.password, user.password);
if (passwordIsValid) {
let token = jwt.sign({ id: user._id }, secret, { expiresIn: 86400 }); // 24 Hours
return res.status(200).send({ auth : true, token: token });
} else {
return res.status(404).send('Not Found');
}
}).select('+password'); // Overrides model's 'select:false' property of the password model's property in order to compare it with given plaintext pwd.
}
};
Примечания
let token = jwt.sign({ id: user._id }, secret, { expiresIn: 86400 });
создает токен на основе id
и ранее определенного secret
, который должен быть очень длинной и случайной строкой, полная целостность и безопасность токена могут быть нарушены слабым secret
.
Это самая базовая конфигурация для JWT, некоторые исследования докажут вам, что этот код не готов к работе. Но в вашем случае это поможет.
Auth Controller
Наконец, вы должны спроектировать промежуточное ПО аутентификации, которое сравнивает предоставленный токен с ранее подписанным. Еще раз, он использует secret
для их сравнения.
const secret = (process.env.TokenSuperSecret) ?
process.env.TokenSuperSecret : 'SuperSecret';
const jwt = require('jsonwebtoken');
const passport = require('passport');
const CustomStrategy = require('passport-custom');
passport.use('jwt', new CustomStrategy(async (req, callback, err) => {
const token = req.headers['x-access-token'];
if (!token) return callback(err);
await jwt.verify(token, secret, (err, decoded) => {
if (err) return callback(err);
req.userId = decoded.id;
callback(null, req);
});
}));
exports.isAuthenticated = passport.authenticate('jwt', {
session: false
}, null);
Примечания
jwt.verify(token, secret, (err, decoded) => {}
возвращает обещание, содержащее decoded userId
(которое вы кодировали jwt.sign
в процессе входа в систему). Не стесняйтесь передавать его своим промежуточным программам, чтобы узнать, какой пользователь в данный момент выполняет запрос.
Так долго, и спасибо за всю рыбу!