Необходимые условия : Javascript ES6
🍁Passport.js
Это промежуточное ПО аутентификации для приложений на базе Express, использующих Node.js. Будучи промежуточным ПО, он имеет доступ к объектам запроса и ответа и может манипулировать ими в цикле запрос-ответ. Библиотека passport.js состоит из двух отдельных библиотек. Первая — это основная библиотека Passport JS, а вторая — соответствующая библиотека стратегии.
- Основная библиотека Passport JS требуется всегда и используется для поддержания информации о сеансе для аутентифицированных пользователей (т.е. вы будете импортировать эту библиотеку независимо от типа стратегии, которую вы будете использовать для аутентификации пользователя).
- Библиотека вторичных стратегий зависит от методики, которую вы планируете использовать для аутентификации пользователя. Например, passport-local, passport-facebook, passport-oauth-google и т.д. Пользователи могут быть аутентифицированы по имени пользователя/паролю, сохраненному в базе данных, которую вы создали локально или в облаке (так называемая локальная стратегия), ИЛИ пользователи могут быть аутентифицированы путем входа в свой аккаунт Google (стратегия аутентификации Google), или Facebook, или Github.
Фреймворк Passport JS абстрагирует процесс входа в систему на две отдельные части: управление сессией (выполняется библиотекой Passport JS) и аутентификация (выполняется вторичной библиотекой Strategy, например, passport-local или passport-facebook или passport-oauth-google и т.д.).
Здесь вступает в действие express.js,
Библиотека Passport JS соединяется с библиотекой express-session и формирует базовые подмостки для присоединения (аутентифицированной) информации о пользователе к объекту req.session. Основная библиотека Passport JS работает с уже аутентифицированными пользователями и не играет никакой роли в фактической аутентификации пользователей. Ее единственная цель — поддерживать (присоединять) (уже аутентифицированного) пользователя к сессиям.
🍁Bcrypt
Очень важно иметь безопасную систему для сохранения пароля в интернет-бизнесе. Сохранять пароль непосредственно в базе данных не рекомендуется. Это может привести к нарушению безопасности. Чтобы сохранить пароль без нарушения безопасности и обеспечить пользователям конфиденциальность их личных данных, необходимо следовать некоторым протоколам при создании приложения.
Обычно разработчики запускают функцию хэширования, чтобы преобразовать пароль в нечто, что выглядит совершенно иначе, чем его первоначальная форма, с помощью математического алгоритма. Этот процесс называется хэшированием, а математический алгоритм — алгоритмом хэширования. Известный пакет node.js — «bcrypt», который скачивают в среднем 590 489 раз в неделю (о да, это много 💰).
Bcrypt также вводит технику соли. Для того чтобы повысить сложность защиты паролей и защитить их от атак, предпринимается дополнительный шаг, называемый солерованием пароля. Соление хэша в области криптографии означает добавление дополнительной строки из 32 или более символов к паролю перед его хэшированием.
Давайте продемонстрируем это на примере.
Майкл и Боб используют один и тот же пароль s@1t3dH@shBrown
по совпадению, у них будет одинаковый хэш: $2a$12$xdWgQ5mhv8rSaUK3qdusTO4XdMFbQi6TD/1VvOZjvGm10RXnhZZa2
.
Однако, если пароль Майклса соленый Iwx2ZE
, а пароль Боба соленый 0DoVej
, у них будут совершенно разные соленые хэши.
Майкл
Пароль: s@1t3dH@shBrown
.
Соль: Iwx2ZE
.
Соленый вход: Iwx2ZEs@1t3dH@shBrown
Выход соленого хэша: $2a$12$TGRg8FCZvnDm.f4WPNtWQucwRv5zsi4D9Qy/gYgpfFfYx9XpXdE6a
Боб
Пароль: s@1t3dH@shBrown
Соль: 0DoVej
Соленый вход: 0DoVejs@1t3dH@shBrown
Выход соленого хэша: $2a$12$VtpXTHf69x1db/71bGHl3eMiEDAkgQe/Gq6UeNOKuHvdg.WnIXEHa
Как вы можете видеть, их соленые хэши совершенно разные, несмотря на то, что они используют один и тот же пароль.
🍁Построим аутентификацию пользователя
🍀Установите необходимый пакет и запустите сервер
Введите npm init
и установите следующие пакеты,
- express
- express-session
- паспорт
- passport-local
- bcryptjs
Теперь создайте файл, app.js
, и добавьте следующий код для создания сервера.
const express = require("express");
const app = express();
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
Вы можете использовать dotenv
для хранения номера PORT.
🍀Установка базы данных
Мы будем использовать mongoDB, поэтому давайте установим ее
npm i mongoose
создадим файл db.js и настроим базу данных
const mongoose = require('mongoose')
function Dbconnect(){
const url = mongodb://localhost:27017/user;
mongoose.connect(url,{ useNewUrlParser: true, useUnifiedTopology: true
}).then(()=>{
console.log('Connection Successful');
}).catch((error)=>{
console.log('Something went wrong', error)
});
}
module.exports = Dbconnect
Теперь создайте файл UserModel.js и добавьте в него следующее.
const mongoose = require('mongoose')
const {Schema} = mongoose
const UserSchema = new Schema ({
email: {
type: String,
required: true
},
password: {
type: String,
required: true
}
})
const UserModel = mongoose.model('user', UserSchema);
module.exports = UserModel;
Прежде чем хранить пароль, его необходимо зашифровать в целях безопасности. Вы будете использовать bcrypt , очень полезный пакет npm, который упрощает работу с зашифрованными паролями.
npm i bcrypt
Измените UserModel.js, чтобы зашифровать пароль перед сохранением в базе данных.
const mongoose = require('mongoose')
const bcrypt = require('bcryptjs');
const {Schema} = mongoose
const UserSchema = new Schema ({
...
})
UserSchema.pre('save', async function(next) {
try {
// check method of registration
const user = this;
if (!user.isModified('password')) next();
// generate salt
const salt = await bcrypt.genSalt(10);
// hash the password
const hashedPassword = await bcrypt.hash(this.password, salt);
// replace plain text password with hashed password
this.password = hashedPassword;
next();
} catch (error) {
return next(error);
}
});
...
const User = mongoose.model('User', UserSchema);
Здесь мы используем хук предварительного сохранения для изменения пароля перед его сохранением. Идея заключается в том, чтобы хранить хэш-версию пароля вместо обычного текста пароля. Хэш — это длинная сложная строка, сгенерированная из обычного текста.
Используйте функцию isModified, чтобы проверить, изменяется ли пароль, поскольку вам нужно хэшировать только новые пароли. Затем сгенерируйте соль и передайте ее вместе с обычным текстовым паролем в метод hash для генерации хэшированного пароля. Наконец, замените обычный пароль хэшированным паролем в базе данных.
В app.js подключитесь к базе данных.
// connect to db
const db = require('./db');
db.connect();
🍀Установка паспорта
Установите Passport и passport-local. Вы будете использовать эти пакеты для регистрации и входа пользователей.
npm i passport
npm i passport-local
Создайте новый файл passportConfig.js и импортируйте passport-local и UserModel.js.
const LocalStraregy = require("passport-local").Strategy;
const User = require("./userModel");
Настройте Passport для обработки регистрации пользователей.
const LocalStrategy = require("passport-local");
const User = require("./userModel");
module.exports = (passport) => {
passport.use(
"local-signup",
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
async (email, password, done) => {
try {
// check if user exists
const userExists = await User.findOne({ "email": email });
if (userExists) {
return done(null, false)
}
// Create a new user with the user data provided
const user = await User.create({ email, password });
return done(null, user);
} catch (error) {
done(error);
}
}
)
);
}
В приведенном выше коде вы проверяете, не используется ли уже данный email. Если email не существует, зарегистрируйте пользователя. Обратите внимание, что вы также настраиваете поле имени пользователя на прием электронной почты. По умолчанию passport-local ожидает имя пользователя, поэтому вам нужно указать ему, что вместо него вы передаете email.
Используйте passport-local для обработки входа пользователя в систему.
module.exports = (passport) => {
passport.use(
"local-signup",
new localStrategy(
...
)
);
passport.use(
"local-login",
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
async (email, password, done) => {
try {
const user = await User.findOne({ email: email });
if (!user) return done(null, false);
const isMatch = await user.matchPassword(password);
if (!isMatch)
return done(null, false);
// if passwords match return user
return done(null, user);
} catch (error) {
console.log(error)
return done(error, false);
}
}
)
);
};
Здесь проверяется, существует ли пользователь в базе данных, и если да, то проверяется, совпадает ли предоставленный пароль с паролем в базе данных. Обратите внимание, что вы также вызываете метод matchPassword() в модели пользователя, поэтому перейдите в файл UserModel.js и добавьте его.
UserSchema.methods.matchPassword = async function (password) {
try {
return await bcrypt.compare(password, this.password);
} catch (error) {
throw new Error(error);
}
};
Этот метод сравнивает пароль от пользователя и пароль в базе данных и возвращает true, если они совпадают.
🍀Настройка маршрутов аутентификации
Теперь вам нужно создать конечные точки, на которые пользователи будут отправлять данные. Первым будет маршрут регистрации, который будет принимать email и пароль нового пользователя.
В app.js для регистрации пользователя используйте только что созданное промежуточное ПО аутентификации паспорта.
app.post("/auth/signup",
passport.authenticate('local-signup', { session: false }),
(req, res, next) => {
// sign up
res.json({
user: req.user,
});
}
);
В случае успеха маршрут регистрации должен вернуть созданного пользователя.
Далее создайте маршрут входа в систему.
app.post(
"/auth/login",
passport.authenticate('local-login', { session: false }),
(req, res, next) => {
// login
res.json({
user: req.user,
});
}
);
🍀Добавление защищенных маршрутов
До сих пор вы использовали Passport для создания промежуточного ПО, которое регистрирует пользователя в базе данных, и другого, которое позволяет зарегистрированному пользователю войти в систему. Далее вы создадите промежуточное ПО авторизации для защиты конфиденциальных маршрутов с помощью JSON web-токена (JWT). Чтобы реализовать авторизацию JWT, вам необходимо:
Сгенерировать токен JWT. Передать токен пользователю. Пользователь отправит его обратно в запросах авторизации. Проверить токен, отправленный пользователем.
Для работы с JWT вы будете использовать пакет jsonwebtoken.
npm i jsonwebtoken
Далее сгенерируйте токен для каждого пользователя, успешно вошедшего в систему.
В файле app.js импортируйте jsonwebtoken и измените маршрут входа, как показано ниже.
app.post(
"/auth/login",
passport.authenticate('local-login', { session: false }),
(req, res, next) => {
// login
jwt.sign({user: req.user}, 'secretKey', {expiresIn: '1h'}, (err, token) => {
if(err) {
return res.json({
message: "Failed to login",
token: null,
});
}
res.json({
token
});
})
}
);
В реальном приложении вы бы использовали более сложный секретный ключ и хранили его в конфигурационном файле.
Маршрут входа возвращает токен в случае успеха.
Используйте passport-jwt для доступа к защищенным маршрутам.
npm i passport-jwt
В файле passportConfig.js настройте passport-jwt.
const JwtStrategy = require("passport-jwt").Strategy;
const { ExtractJwt } = require("passport-jwt")
module.exports = (passport) => {
passport.use(
"local-login",
new LocalStrategy(
...
);
passport.use(
new JwtStrategy(
{
jwtFromRequest: ExtractJwt.fromHeader("authorization"),
secretOrKey: "secretKey",
},
async (jwtPayload, done) => {
try {
// Extract user
const user = jwtPayload.user;
done(null, user);
} catch (error) {
done(error, false);
}
}
)
);
};
Обратите внимание, что вы извлекаете JWT из заголовка авторизации, а не из тела запроса. Это не позволит хакерам перехватить запрос и получить токен.
Теперь вы готовы вывести аутентификацию пользователей на новый уровень.
Надеюсь, вам понравился этот небольшой учебник. Счастливого кодинга💗
Всегда помните, что никто не достиг вершины одним выстрелом. Им потребовалось гораздо больше борьбы и тяжелой работы, чем вы можете себе представить. Так что стремитесь к знаниям и продолжайте двигаться вперед. Спасибо