Содержание


Овладение MEAN-программированием

Управление аутентификацией с помощью OAuth и Passport

Comments

Серия контента:

Этот контент является частью # из серии # статей: Овладение MEAN-программированием

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Овладение MEAN-программированием

Следите за выходом новых статей этой серии.

Приложение User Group List and Information (UGLI) начинает обретать привлекательную форму. Теперь можно демонстрировать локальный контент, созданный посредством экранов CRUD, которые мы настроили в статье MEAN и CRUD-приложение UGLI с динамичным веб-дизайном. А также получать информацию с внешнего сайта с помощью служб, разработанных в статье Объединение MEAN с Meetup.com и микроданными.

Важная часть этого проекта – обмен информацией о конференциях с широкой общественностью. Но как руководитель группы пользователей, я также хочу ограничить круг участников некоторых мероприятий только зарегистрированными членами. Например, я мог бы сохранить некоторую благопристойность комментариев к нашим презентациям, отключив анонимный доступ и потребовав регистрации. Поэтому в этот раз мы воспользуемся службой OAuth на сайте Meetup.com, чтобы обеспечить возможность регистрации в приложении UGLI. За примером кода обращайтесь к разделу Загрузки.

Создание новой учетной записи пользователя

Нажав кнопку Sign up (см. рисунок 1), пользователи нашего приложения могут создать новую учетную запись, которая хранится локально в базе данных MongoDB. Эта функциональность встроена в приложение — никакого дополнительного программирования не требуется.

Рисунок 1. Страница регистрации UGLI
Скриншот страницы регистрации UGLI
Скриншот страницы регистрации UGLI

Это поведение по умолчанию, несомненно, – простейшее решение с точки зрения разработки, но с точки зрения удобства для пользователя оно оставляет желать лучшего. У членов группы пользователей уже есть учетная запись на Meetup.com, которую они используют для RSVP предстоящих конференций. Необходимость создавать и поддерживать дублирующий набор учетных данных не просто раздражает — это вопиющее нарушение принципа «Не повторяйся» (Don't Repeat Yourself – DRY).

К счастью, благодаря комплексу MEAN, который мы используем, можно создать решение распределенной аутентификации и авторизации с использованием служб OAuth и Passport. Проще говоря, ваши пользователи могут войти (аутентифицироваться) в приложение UGLI с теми же учетными данными, которые они используют для входа на сайт Meetup.com. Но это нельзя сделать без разрешения пользователей; они должны разрешить приложению UGLI (авторизовать его) использовать их реквизиты доступа в Meetup.com.

Но даже после того, как приложение UGLI будет авторизовано посредством OAuth, эти реквизиты доступа не попадут в авторизованное приложение. Оно не хранит дубликаты имен и паролей пользователей локально. Авторизуемое приложение (UGLI) перенаправляет пользователей к поставщику услуг OAuth (Meetup.com), где они вводят свои учетные данные (имя пользователя и пароль). После успешной авторизации возвращается маркер доступа к авторизованному приложению.

Эта схема исключает из приложения огромный кусок кода и логики. Вам уже не нужно беспокоиться о хранении зашифрованных паролей на своем сервере — теперь это проблема поставщика услуг OAuth. Аналогично, вам не нужно писать алгоритмы для обеспечения надежных паролей, разбираться с забытыми паролями или заставлять пользователей периодически изменять свои пароли.

Таким образом, OAuth уменьшает число паролей, которые нужно запоминать пользователям, и значительно уменьшает требуемый объем кода. Разве не это классическое определение беспроигрышного сценария?

Знакомство с OAuth и Passport

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

OAuth – это открытый стандарт распределенной аутентификации и авторизации. Он разработан в 2006 году компанией Twitter и ее бизнес-партнером Ma.gnolia для облегчения создания настольных виджетов, отображающих сведения из аутентифицированных служб. С тех пор OAuth внедрен сотнями крупных веб-сайтов от Google до Facebook, Twitter, GitHub, LinkedIn и многих других. (См. список известных поставщиков услуг OAuth).

Passport — это библиотека OAuth для Node.js. В частности, это промежуточное ПО, которое гладко интегрируется с приложениями Express. Имеется более 140 плагинов Passport (так называемых стратегий), оптимизированных для каждого поставщика услуг OAuth.

Если открыть файл package.json приложения UGLI в текстовом редакторе (как показано в листинге 1), можно увидеть стратегии Passport для четырех основных служб — Facebook, Twitter, LinkedIn и Google — наряду с локальной стратегией для хранения учетных данных непосредственно в MongoDB.

Листинг 1. Стратегии Passport в package.json
"dependencies": {
    "passport": "~0.2.0",
    "passport-local": "~1.0.0",
    "passport-facebook": "~1.0.2",
    "passport-twitter": "~1.0.2",
    "passport-linkedin": "~0.1.3",
    "passport-google-oauth": "~0.1.5"
}

Используя в качестве руководства эти готовые примеры, мы добавим шестую стратегию для включения Meetup.com качестве поставщика услуг OAuth для UGLI.

Установка стратегии Passport для Meetup.com

На странице Authenticating посредством Meetup API видно, что Meetup.com предлагает услуги OAuth. Быстрый веб-поиск по ключевым словам meetup.com passport.js strategy даст ссылку на библиотеку, которую мы ищем: passport-meetup.

Введите npm install passport-meetup --save, чтобы загрузить библиотеку в node_modules, и отредактируйте блок зависимостей в файле package.json.

Это была самая простая часть. Имея стратегию, следующим шагом нужно включить ее в страницы регистрации и входа.

Добавление ссылки Meetup.com на страницы регистрации и входа

Откройте файл public/modules/users/views/signup.client.view.html в текстовом редакторе. В верхней части файла видны ссылки на различных поставщиков OAuth-услуг (как показано в листинге 2).

Листинг 2. public/modules/users/views/signup.client.view.html
<h3 class="col-md-12 text-center">Sign up using your social accounts</h3>
<div class="col-md-12 text-center">
    <a href="/auth/facebook" class="undecorated-link">
        <img src="/modules/users/img/buttons/facebook.png">
    </a>
    <a href="/auth/twitter" class="undecorated-link">
        <img src="/modules/users/img/buttons/twitter.png">
    </a>
    <a href="/auth/google" class="undecorated-link">
        <img src="/modules/users/img/buttons/google.png">
    </a>
    <a href="/auth/linkedin" class="undecorated-link">
        <img src="/modules/users/img/buttons/linkedin.png">
    </a>
</div>

Замените существующие ссылки той, которая указывает на пока еще не созданный маршрут /auth/meetup и отображает пока еще не загруженный значок Meetup.com (как показано в листинге 3).

Листинг 3. Ссылка на auth/meetup
<h3 class="col-md-12 text-center">Sign up using your Meetup.com account</h3>
<div class="col-md-12 text-center">
    <a href="/auth/meetup" class="undecorated-link">
        <img src="/modules/users/img/buttons/meetup.png">
    </a>
</div>

Посетите страницу Meetup Icon и сохраните в папке public/modules/users/img/buttons/, где хранятся значки других социальных сетей, изображение размером 128 x 128 пикселей.

Теперь, когда страница регистрации готова, откройте файл public/modules/users/views/signin.client.view.html в текстовом редакторе и настройте его так же, как страницу регистрации (см. листинг 4).

Листинг 4. public/modules/users/views/signin.client.view.html
<h3 class="col-md-12 text-center">Sign in using your Meetup.com account</h3>
<div class="col-md-12 text-center">
    <a href="/auth/meetup" class="undecorated-link">
        <img src="/modules/users/img/buttons/meetup.png">
    </a>
    </div>

Если все пойдет, как запланировано, то ваша новая страница регистрации будет выглядеть как на рисунке 2. Конечно, при отсутствии маршрута при попытке нажать на ссылку вы получите сообщение об ошибке 404 «Страница не найдена». Мы исправим это позже.

Рисунок 2. Новая страница регистрации UGLI
Скриншот новой страницы регистрации UGLI
Скриншот новой страницы регистрации UGLI

Настройка маршрутов auth/meetup со стороны сервера

Следующим шагом мы создадим маршруты auth/meetup со стороны сервера. Напомним, что вся логика со стороны сервера хранится в каталоге app; а логики со стороны клиента – в каталоге public.

Откройте файл app/routes/users.server.routes.js в текстовом редакторе. Найдите блок кода для Facebook и скопируйте его, заменив facebook на meetup (как показано в листинге 5).

Листинг 5. app/routes/users.server.routes.js
// Настройка маршруты oauth facebook
app.route('/auth/facebook').get(passport.authenticate('facebook', {
    scope: ['email']
}));
app.route('/auth/facebook/callback').get(users.oauthCallback('facebook'));

// Настройка маршрутов oauth meetup
app.route('/auth/meetup').get(passport.authenticate('meetup', {
    scope: ['email']
}));
app.route('/auth/meetup/callback').get(users.oauthCallback('meetup'));

Помните гиперссылки на auth/meetup, созданные вами на страницах регистрации и входа в предыдущем разделе? Первый маршрут (auth/meetup) вызывается, когда пользователь, нажав на ссылку, отправляет на сервер запрос HTTP GET. Passport пытается выполнить аутентификацию пользователя, применяя стратегию passport-meetup. Результаты попытки входа (успешная или нет) асинхронно отправляются по второму маршруту auth/meetup/callback.

Если сейчас нажать на ссылку Meetup на странице регистрации, вы получаете вместо сообщения об ошибке 404 сообщение 500 «Ошибка сервера». Это не намного лучше, но хоть какой-то прогресс. Перейдем к настройке стратегии Meetup.

Настройка стратегии Meetup

Все стратегии Passport можно найти в одноименном каталоге config/strategies. Скопируйте файл facebook.js в meetup.js, затем откройте файл meetup.js в текстовом редакторе.

Нужно просмотреть этот файл и во всех местах заменить facebook на meetup, как мы делали в предыдущем разделе. Но это не простая операция поиска/замены. Необходимо также внести несколько незначительных изменений в конфигурацию.

Для начала измените в верхней части файла необходимые библиотеки из стратегии Facebook на библиотеки Meetup (см. листинг 6).

Листинг 6. config/strategies/meetup.js
/**
 * Зависимости модуля.
 */
var passport = require('passport'),
    url = require('url'),
    MeetupStrategy = require('passport-meetup').Strategy,
    config = require('../config'),
    users = require('../../app/controllers/users');

Затем нужно настроить блок параметров, которые передаются в новую стратегию. Эти значения зависят от стратегии. В листинге 7 показаны параметры стратегии Facebook, которые не будут работать для Meetup.

Листинг 7. Параметры Facebook, которые не работают в Meetup
module.exports = function() {
    // Использование стратегии facebook
    passport.use(new FacebookStrategy({
            clientID: config.facebook.clientID,
            clientSecret: config.facebook.clientSecret,
            callbackURL: config.facebook.callbackURL,
            passReqToCallback: true
        },

К счастью, в модуль passport-meetup, который мы установили (npm install) ранее, включен пример кода. Откройте в текстовом редакторе файл node_modules/passport-meetup/examples/login/app.js. Найдите passport.use function call (см. листинг 8).

Листинг 8. node_modules/passport-meetup/examples/login/app.js
passport.use(new MeetupStrategy({
    consumerKey: MEETUP_KEY,
    consumerSecret: MEETUP_SECRET,
    callbackURL: "http://127.0.0.1:3000/auth/meetup/callback"
  },

Скопируйте этот фрагмент в файл meetup.js, переписав код Facebook. Далее, измените значения справа от двоеточия значениями, показанными в листинге 9.

Листинг 9. Параметры для Meetup
passport.use(new MeetupStrategy({
    consumerKey: config.meetup.consumerKey,
    consumerSecret: config.meetup.consumerSecret,
    callbackURL: config.meetup.callbackURL,
    },

В следующем разделе мы получим от Meetup.com ключи consumerKey и consumerSecret и сохраним их в файле config.js. Но перед этим нужно сделать еще несколько изменений в текущем файле.

Функция, следующая за конструктором new MeetupStrategy, – это обработчик событий, который получает ответы от Meetup.com. Нам интересны три основных части ответа: маркер доступа, маркер обновления и профиль пользователя. (См. боковую врезку Маркеры доступа и маркеры обновления OAuth).

Маркер доступа и маркер обновления – это строки, которые в неизмененном виде передаются в Passport. Хотя они имеют решающее значение для успеха операции OAuth, ничего интересного в них нет. (Примеры приведены в листинге 10).

Гораздо интереснее профиль пользователя. Это объект JSON, возвращаемый поставщиком услуг OAuth, который содержит информацию об успешно аутентифицированном пользователе. Конкретные детали зависят от поставщика услуг OAuth. В листинге 10 приведен пример профиля пользователя, возвращенного Meetup.

Листинг 10. Профиль пользователя, возвращенный Meetup.com поставщиком услуг OAuth
{ 
  provider: 'meetup',
  id: 13848777,
  displayName: 'Scott Davis',
  _raw: '{ 
      "results": [{
        "status": "active",
        "link": "http:\\\/\\\/www.meetup.com\\\/members\\\/13848777",
        "photo": {
          "photo_link": "http:\\\/\\\/photos1.meetupstatic.com\\\/photos\\\/member\\\/7\\\/4\\\/d\\\/2\\
\/member_11849906.jpeg",
          "thumb_link": "http:\\\/\\\/photos3.meetupstatic.com\\\/photos\\\/member\\\/7\\\/4\\\/d\\\/2\\
\/thumb_11849906.jpeg",
          "photo_id": 11849906
        },
        "country": "us",
        "state": "CO",        
        "city": "Denver",
        "id": 13848777,
        "joined": 1295844957000,
        "bio": "Scott Davis is the founder of ThirstyHead.com, a training and 
consulting company that specializes in leading-edge technology solutions like 
HTML 5, NoSQL, Groovy, and Grails.",
        "name": "Scott Davis",
        "other_services": {
          "twitter": {
            "identifier": "@scottdavis99"
          }
        }
      }]  
   }',
  _json: { 
     results: [ [Object] ],
     meta: { 
        link: 'https://api.meetup.com/2/members',
        total_count: 1,
        url: 'https://api.meetup.com/2/members?order=name&member_id=13848777&offset=0
&format=json&page=800',
        title: 'Meetup Members v2',
        updated: 1392763702000,
        description: 'API method for accessing members of Meetup Groups',
        method: 'Members',
     },
     accessToken: 'c7b5577bb80aab55439785cd86abcdef',
     refreshToken: '2af98db68950235a1e2519a734abcdef' 
  } 
}

Как видите, Meetup возвращает информацию о пользователе, такую как имя, место жительства, дата вступления, фотографии, привязанные учетные записи в социальных сетях и т.п.

Последнее, что нужно сделать, чтобы завершить настройку стратегии Meetup – отобразить поля профиля Meetup обратно на объект Mongoose User, определенный в файле app/models/user.server.model.js. Отредактируйте оставшийся блок Facebook в файле config/strategies/meetup.js, как показано в листинге 11.

Листинг 11. Отображение профиля пользователя OAuth на объект User
// Создание профиля пользователя OAuth
var providerUserProfile = {
    firstName: '',
    lastName: '',
    displayName: profile.displayName,
    email: '',
    username: profile.id,
    provider: profile.provider,
    providerIdentifierField: 'id',
    providerData: providerData
};

Если в профиле JSON Meetup есть поля, которые вам хотелось бы добавить к объекту User, то сейчас самое подходящее время это сделать. Не забудьте добавить новые поля и в HTML-формы в каталоге public/modules/users/views.

Готовый файл config/strategies/meetup.js должен выглядеть как в листинге 12.

Листинг 12. Готовый файл config/strategies/meetup.js
'use strict';

/**
 * Зависимости модуля.
 */
var passport = require('passport'),
    url = require('url'),
    MeetupStrategy = require('passport-meetup').Strategy,
    config = require('../config'),
    users = require('../../app/controllers/users');

module.exports = function() {
    // Использование стратегии meetup
    passport.use(new MeetupStrategy({
        consumerKey: config.meetup.clientID,
        consumerSecret: config.meetup.clientSecret,
        callbackURL: config.meetup.callbackURL,
        },
        function(req, accessToken, refreshToken, profile, done) {
            // Задание поставщика данных и включение маркеров

            var providerData = profile._json;
            providerData.accessToken = accessToken;
            providerData.refreshToken = refreshToken;

            // Создание профиля пользователя OAuth
            var providerUserProfile = {
                firstName: '',
                lastName: '',
                displayName: profile.displayName,
                email: '',
                username: profile.id,
                provider: profile.provider,
                providerIdentifierField: 'id',
                providerData: providerData
            };

            // Сохранение профиля пользователя OAuth
            users.saveOAuthUserProfile(req, providerUserProfile, done);
        }
    ));
};

Прежде чем протестировать этот код, нужно сделать еще одну вещь: получить от Meetup ключи consumerKey и consumerSecret.

Получение от Meetup ключей consumerKey и consumerSecret

До сих пор я говорил только об аутентификации пользователя. Но прежде чем пользователи смогут войти в UGLI с помощью OAuth, вы (разработчик) должны представить доказательство того, что ваша организация – та, за которую себя выдает. Для этого пользователю предоставляется открытый ключ (consumerKey). Вашему приложению также необходимо знать свой секретный ключ (consumerSecret).

Те, кто уже работал с Инфраструктурой открытых ключей (PKI), знают, что этот ключ важно хранить в секрете. Если кто-то узнает ваш секретный ключ, он сможет маскироваться под вашу организацию. И наоборот, если вы не передадите свой открытый ключ пользователям, они не смогут убедиться, что вы – это вы.

Организатор группы пользователей на Meetup.com может создать ключи consumerKey и consumerSecret на странице Meetup Your OAuth Consumers (см. рисунок 3). В качестве Consumer Name я использовал HTML5 Denver User Group, в качестве Application Website – http://www.meetup.com/HTML5-Denver-Users-Group/ и в качестве Redirect URI – http://localhost:3000/auth/meetup/callback. После публикации приложения UGLI я изменил значение Application Website на http://html5denver.com, а Redirect URI – на http://html5denver.com/auth/meetup/callback.

Рисунок 3. Создание consumerKey и consumerSecret для организации HTML5 Denver
Скриншот создания consumerKey и consumerSecret для HTML5 Denver
Скриншот создания consumerKey и consumerSecret для HTML5 Denver

Если у вас нет группы пользователей на Meetup.com, то у вашей учетной записи нет прав на генерацию OAuth-ключей от имени группы. Однако вы можете создавать маркеры для одной из своих учетных записей в других социальных сетях и соответствующим образом скорректировать действия, описанные в этой статье. Используйте инструкцию Реализации входа через Twitter, чтобы создать ключи приложения для своей учетной записи Twitter, или инструкцию Маркеры доступа, чтобы использовать учетную запись Facebook. Краткий веб-поиск по ключевым словам your social media website oauth keys должен привести к пошаговым инструкциям.

Добавление OAuth-ключей организации к приложению

Получив два ключа (открытый и секретный), добавим их к приложению посредством переменные среды — так же, как мы изменили значение PORT в статье Тур по MEAN-приложению.

Напоминаем, что можно задавать переменные, которые изменяются в зависимости от режима работы: development, production или test. Значения, зависящие от среды, хранятся в папке config/env. Откройте файл config/env/development.js в текстовом редакторе. Скопируйте и вставьте блок Facebook и отредактируйте его для Meetup (как показано в листинге 13). Убедитесь, что имена атрибутов соответствуют тем, которые использовались в вызове функции passport.use в файле config/strategies/meetup.js.

Листинг 13. config/env/development.js
'use strict';

module.exports = {
    db: 'mongodb://localhost/test-dev',
    app: {
        title: 'HTML5 Denver'
    },

    meetup: {
        consumerKey: process.env.MEETUP_KEY || 'APP_ID',
        consumerSecret: process.env.MEETUP_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/meetup/callback'
    },    
    facebook: {
        clientID: process.env.FACEBOOK_ID || 'APP_ID',
        clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/facebook/callback'
    },
    twitter: {
        clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
        clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
        callbackURL: 'http://localhost:3000/auth/twitter/callback'
    },
    google: {
        clientID: process.env.GOOGLE_ID || 'APP_ID',
        clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/google/callback'
    },
    linkedin: {
        clientID: process.env.LINKEDIN_ID || 'APP_ID',
        clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/linkedin/callback'
    }
};

APP_ID и APP_SECRET можно заменить жестко запрограммированными значениями consumerKey и consumerSecret, полученными в предыдущем разделе. Но более надежное решение – передать эти значения приложению UGLI через переменные среды. Чтобы запустить приложение с ключами consumerKey и consumerSecret своей организации, введите следующую команду:

MEETUP_KEY=l75fkklhurkack36eelfhhfhjc MEETUP_SECRET=abcdeg316jd3ni43f21u1abcde NODE_ENV=development grunt

Не забудьте внести аналогичные изменения в файл config/env/production.js. И если вы уже создали учетную запись пользователя, не забудьте удалить ее из базы данных html5-denver-dev в MongoDB, чтобы можно было пройти весь процесс создания новой учетной записи снова.

Заключение

Ларри Уолл (создатель языка программирования Perl) сказал: «Простые вещи должны быть простыми, а сложные – возможными». Я надеюсь, что это высказывание подводит хороший итог вашему опыту подключения служб OAuth и Passport для использования Meetup.com как средства распределенной аутентификации и авторизации.

В следующей статье из серии Овладение MEAN-программированием я познакомлю вас с процессом введения в стек MEAN инфраструктуры тестирования. Вы узнаете о службе тестирования на стороне сервера Mocha, службе тестирования на стороне клиента Jasmine и службе запуска тестов на разных браузерах Karma. Пока же попрактикуйтесь в MEAN-программировании.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, Open source
ArticleID=1008879
ArticleTitle=Овладение MEAN-программированием: Управление аутентификацией с помощью OAuth и Passport
publish-date=06082015