Я работал над аутентификацией для веб-приложения, когда обнаружил, что не существует простого руководства по интеграции веб-токенов 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. Именно здесь происходит большая часть волшебства, поскольку мы будем проверять учетные данные пользователя, создавать токены и передавать токен клиенту.

Локальный вход
Ваш локальный вход будет состоять из трех основных частей.

  1. Логин POST-маршрут
  2. ПО промежуточного слоя аутентификации
  3. Пользователь 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 требуется два маршрута.

  1. Маршрут получения, который инициирует процесс 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, настройка будет практически такой же.