Я работал над аутентификацией для веб-приложения, когда обнаружил, что не существует простого руководства по интеграции веб-токенов JSON и OAuth. Было доступно множество отдельных руководств, но заставить их работать вместе оказалось немного сложно, особенно потому, что выбранное решение OAuth, Passport, использует пользовательские сеансы вместо токенов. В качестве обходного пути я перехватил использование Passport req.user, чтобы иметь возможность отправлять токен обратно клиенту. Это хорошо работает с моей локальной аутентификацией, которая также устанавливает аутентифицированного пользователя в объект req.user. Надеемся, что это базовое руководство поможет разработчикам, желающим объединить аутентификацию по локальному токену с OAuth.
Для начала я использую Express для своего сервера, маршрутов и локального входа в систему; Паспорт для ПО промежуточного слоя OAuth и JWT-Simple для создания моих токенов. PostgreSQL и Sequelize — моя база данных и ORM соответственно.
Учебник разбит на следующие части:
База данных: настройка модели пользователя
Сервер: обработка локальной аутентификации и Passport OAuth
База данных
Создайте модель пользователя, в которой хранятся имя, пароль, идентификатор googleId и токен доступа. Вы будете обращаться к этой пользовательской модели в своих маршрутах, поэтому обязательно импортируйте ее в файлы своего сервера.
//user model
const User = conn.define('user', {
name : {
type : Sequelize.STRING,
allowNull : false,
unique : true,
validate : {
notEmpty : true
}
},
password : {
type : Sequelize.STRING,
allowNull : false,
unique : true,
validate : {
notEmpty : true
}
},
googleId : {
type : Sequelize.STRING
},
accessToken : {
type : Sequelize.STRING
}
});
Сервер
Пришло время настроить локальный вход и аутентификацию OAuth. Именно здесь происходит большая часть волшебства, поскольку мы будем проверять учетные данные пользователя, создавать токены и передавать токен клиенту.
Локальный вход
Ваш локальный вход будет состоять из трех основных частей.
- Логин POST-маршрут
- ПО промежуточного слоя аутентификации
- Пользователь GET маршрут
Логин POST Маршрут
Первым шагом является настройка нашего локального маршрута входа. Для этого я создам отдельный файл в папке «Мои маршруты» с именем «auth.js» для хранения маршрутов входа в систему. Обязательно импортируйте файл auth.js в файл app.js.
Маршрут предназначен для проверки учетных данных пользователя. Если это действительный пользователь, мы создадим токен с помощью jwt-simple и отправим его обратно клиенту.
Сначала импортируйте jwt-simple, а затем создайте маршрут.
npm install jwt-simple —save
//in app.js and auth.js file
const jwt = require('jwt-simple’);
router.post('/', (req, res, next) => {
const { name, password } = req.body;
User.findOne({
where : { name, password }
})
.then( user => {
//token is created by providing JWT with the user.id to encode along with the secret
const token = jwt.encode({ id : user.id }, 'your_JWT_SECRET' );
//send token back to the client
res.send({ token });
})
});
ПО промежуточного слоя аутентификации
Далее нам понадобится промежуточное программное обеспечение, которое проверяет каждый запрос токена. Если есть токен, то он будет декодирован, и идентификатор пользователя будет использован для поиска пользователя и установки его в req.user.
app.use((req, res, next) => {
const token = req.headers.authorization;
if(!token) { return next() }
let id;
try {
id = jwt.decode(token, 'your_JWT_SECRET').id;
User.findById(id)
.then( user => {
if(!user) {
return next({ status : 401 });
}
req.user = user;
next()
})
}
catch(ex) {
next({ status : 401 })
}
});
Пользовательский маршрут GET — используется для постоянных входов в систему.
Мы используем этот маршрут для получения информации о пользователе для того, кто уже вошел в систему и был установлен токен. Этот маршрут довольно простой — он просто проверяет, установлен ли для пользователя req.user.
router.get('/', (req, res, next) => {
if(!req.user) {
return next({ status : 401 });
}
res.send(req.user);
});
OAuth с использованием Passport
А вот и сложная часть. Хотя Passport — это фантастический модуль, который действительно упрощает настройку различных стратегий аутентификации для разных провайдеров (Google, FB, Pinterest и т. д.), он опирается на пользовательские сеансы. Чтобы это работало с веб-токенами JSON, мы перехватим использование Passport req.user для отправки токена обратно клиенту.
В этом руководстве используется стратегия Google Passport, но она должна работать с любой из стратегий Passport OAuth.
Модули: в этой части мы будем использовать паспорт и паспорт-google-oauth.
- npm установить паспорт паспорт-google-oauth — сохранить
- в файле app.js — константный паспорт = require(‘паспорт’)
- В файле oauth.js — const certificate = require(‘passport’) и const GoogleStrategy = require(‘passport-google-oauth’).OAuth2Strategy
Шаги:
1. Чтобы паспорт заработал, нашему серверу необходимо инициализировать его.
//in app.js file app.use(passport.initialize());
2. Создайте еще один файл в папке наших маршрутов с именем oauth.js. Не забудьте также импортировать его в файл app.js.
В нашем файле oauth.js мы собираемся сделать несколько вещей.
- Настройте стратегию OAuth с нашими учетными данными, областью действия и информацией обратного вызова.
- Создайте маршруты, которые запускают процесс OAuth и обрабатывают обратный вызов
- Установите токен на req.user, чтобы мы могли получить его и передать клиенту.
Настройка стратегии
Все стратегии OAuth Passport работают одинаково. Мы собираемся использовать метод использования паспорта для определения необходимых учетных данных и обратного вызова проверки, который принимает учетные данные и предоставляет профиль пользователя Google. Мы можем использовать этот профиль, чтобы найти или создать пользователя в нашей базе данных, а также создать токен.
Как только пользователь найден или создан, токен создается с использованием идентификатора пользователя и присваивается объекту с именем userData. Когда функция VerificationCallback вызывает done(null, userData), она предоставляет Passport аутентифицированного пользователя, который затем передается методам serializeUser и deserializeUser. Эти методы по существу преобразуют хранящиеся пользовательские данные в строку символов, а затем помещают их в req.user, где мы сможем получить доступ к токену через наш маршрут обратного вызова (подробнее обсуждается ниже).
const googleCredentials = {
clientID: 'your-google-clientID',
clientSecret: 'your-google-clientSecret',
callbackURL: '/api/google/callback',
state : false
};
const verificationCallback = (accessToken, refreshToken, profile, done) => {
//console.log('callback profile: ', profile)
const info = {
name : profile.displayName,
password : 'oAuth',
accessToken : accessToken
};
User.findOrCreate({
where : { googleId : profile.id },
defaults : info
})
.then(user => {
//the promise returns the data in an object with the key dataValues in an array
const userObj = user[0].dataValues;
const token = jwt.encode({ id : userObj.id }, 'your_JWT_SECRET' );
const userData = { token : token };
done(null, userData);
})
};
const strategy = new GoogleStrategy(googleCredentials, verificationCallback);
passport.use(strategy);
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
Создание маршрутов
Для обработки аутентификации OAuth требуется два маршрута.
- Маршрут получения, который инициирует процесс OAuth для пользователя.
Этот маршрут загрузит меню входа для определенного провайдера на клиенте. Маршрут использует метод аутентификации паспорта, чтобы указать, какого провайдера вы используете, и объем запрашиваемой информации.
2. Маршрут обратного вызова, на который Google перенаправляет пользователя после завершения аутентификации OAuth.
Маршрут обратного вызова также использует пользовательский обратный вызов для получения токена из объекта req.user и перенаправляет клиента на домашнюю страницу с токеном, добавленным в качестве параметра строки запроса. В нашем клиентском коде мы получим токен. Нам также нужно включить { session : false } в метод аутентификации, чтобы Passport знал, что мы не используем сеансы.
//get route - this is the route that users hit when they click Sign In With Google
router.get('/', passport.authenticate('google', { scope : 'email' }));
//callback route
router.get('/callback',
passport.authenticate('google', { session : false, failureRedirect : '/login' }), (req, res) => {
var token = req.user.token;
res.redirect('/?token=' + token)
});
На этом настройка серверной части завершена! Подводя итог, наш локальный маршрут входа проверяет учетные данные пользователя и создает токен, если пользователь действителен. Токен отправляется обратно клиенту. Passport обрабатывает OAuth, и мы используем предоставленную информацию о пользователе, чтобы найти или создать пользователя и создать токен, который передается в serializeUser и deserializeUser для добавления в req.user. Затем маршрут обратного вызова oauth добавляет токен в качестве параметра запроса и передает его клиенту.
От клиента приложение может установить маркер в локальном хранилище и передать его для последующих запросов. Я не вдавался в подробности настройки клиента, так как это отдельная отдельная тема, и у разработчиков могут быть свои предпочтения, но вы увидите мое клиентское решение, если заглянете в репозиторий github.
Для всех запросов наше промежуточное ПО аутентификации проверяет токен и устанавливает соответствующего пользователя в объект req.user. Это можно использовать для защиты маршрутов, для которых требуется вошедший в систему пользователь.
В Passport есть стратегия локальной аутентификации, но я хотел поиграть с написанием собственного маршрута локальной аутентификации. Однако, если вы хотите использовать локальную стратегию аутентификации Passport, настройка будет практически такой же.