Статьи

Интеграция OAuth с использованием Hapi

Защита веб-ресурсов часто является сложной и сложной задачей. Настолько, что его часто оставляют до последнего этапа разработки, а затем бросают и не делают должным образом. Это понятно, хотя; безопасность — это очень специализированная область в разработке, и большинство людей лишь обдумывают это: «да, это, вероятно, должно быть защищено…». Таким образом, разработчики быстро объединяют специальный метод безопасности:

if (password === "password1") { setCookie(); } else { send(401); } 

и отправить продукт, полный дыр в безопасности. Надеемся, что этот фрагмент — грубое упрощение, но суть все еще в силе.

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

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

Предпосылки

Предполагается, что читатель:
1. имеет функциональное понимание работы с фреймворком hapi server.
2. имеет встроенные веб-ресурсы в прошлом.
3. имеет базовое понимание куки.
4. имеет учетную запись GitHub.
5. имеет элементарное понимание того, что такое Клятва и для чего она используется (вы можете начать с чтения статьи Википедии об этом).

Если какое-либо из этих предположений не соответствует действительности, вам настоятельно рекомендуется сначала разобраться с перечисленными предварительными условиями, и они вернутся, чтобы узнать о защите вашей веб-страницы.

Начиная

Первое, что вам нужно сделать, это создать приложение GitHub. Этот процесс даст вам и ClientID и ClientSecret — оба значения, которые вам понадобятся для настройки OAuth на вашем веб-сервере.

  1. Войдите в свою учетную запись GitHub и перейдите на страницу настроек (https://github.com/settings/profile).
  2. Нажмите на «Приложения»
  3. Нажмите кнопку «Создать новое приложение», и вы перейдете на новый экран, который выглядит следующим образом:
    Зарегистрируйте новое приложение OAuth
  4. Имя приложения и описание приложения могут быть любыми. Для URL домашней страницы и URL обратного вызова авторизации , давайте установим их на локальный сервер, с которым мы будем работать. В моем примере я буду использовать порт 9001, поэтому установите оба значения на «http: // localhost: 9001». Моя полная настройка выглядит так:
    Завершена настройка приложения
  5. После того, как вы нажмете «Зарегистрировать приложение», вы будете перенаправлены на новый экран, в котором будут указаны как ClientID и ClientSecret . Запишите эти значения на потом.

Резюме

Этот шаг был чисто административным. Мы создали новое приложение GitHub, о котором пользователей будут спрашивать, когда они попытаются войти на ваш сайт. Вместо того чтобы доверять http: // localhost: 9001 с нашими учетными данными GitHub, мы будем доверять приложению GitHub для аутентификации пользователей, а затем перезвоним на наш веб-сайт, когда это будет сделано.

Планирование сервера

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

На домашнем маршруте, если пользователь прошел аутентификацию, давайте напечатаем его имя, в противном случае это общее сообщение. Для маршрута аккаунта мы покажем всю информацию, которую нам отправляет GitHub. Если пользователь запрашивает страницу учетной записи без предварительной проверки подлинности, мы ответим правильным кодом состояния 401. Маршрут входа будет доступен для GitHub, попросите пользователя разрешить нашему приложению GitHub доступ к некоторым сведениям об учетной записи. , а затем вернитесь на наш локальный веб-сервер. Наконец, маршрут выхода из системы приведет к выходу пользователя с нашего веб-сайта.

Скелет сервера

Давайте сначала разберемся с шаблонами и настройками маршрутов.

 var Hapi = require('hapi'); var server = new Hapi.Server(); server.connection({ port: 9001 }); server.register([], function (err) { if (err) { console.error(err); return process.exit(1); } server.route([{ method: 'GET', path: '/login', config: { handler: function (request, reply) { // Reach out to GitHub, ask the user for permission for their information // if granted, response with their name reply(); } } }, { method: 'GET', path: '/account', config: { handler: function (request, reply) { // Show the account information if the have logged in already // otherwise, send a 491 reply(); } } }, { method: 'GET', path: '/', config: { handler: function (request, reply) { // If the user is authenticated reply with their user name // otherwise, replay back with a generic message. reply(); } } }, { method: 'GET', path: '/logout', config: { handler: function (request, reply) { // Clear the session information reply.redirect(); } } } ]); server.start(function (err) { if (err) { console.error(err); return process.exit(1); } console.log('Server started at %s', server.info.uri); }); }); 

Сервер скелета перечисления 1

Резюме

Приведенный выше код создает сервер, соединение через порт 9001 , и добавляет некоторые маршруты с заглушенными функциями-обработчиками. Вы заметите server.register([], function() {...} , мы пропускаем пустой массив. По мере продолжения мы начнем добавлять плагины в hapi, но для начального шаблона мы оставим их отключенными). Мы используем server.route чтобы указать четыре маршрута, которые мы хотели построить, и передать им path строку method и объект config Объект config будет интенсивно использоваться в следующих разделах. На данный момент мы отвечаем на каждый маршрут с пустым ответом. Если вы запустите сервер, вы должны увидеть:

 Server started at http://hostname.local:9001 

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

Ничто в этом шаблоне не должно удивлять, если вы работали с хапи в прошлом. Если нет, зайдите на сайт документации здесь, чтобы прояснить ситуацию.

Подключить

Одна из лучших частей хапи — система плагинов. Плагины позволяют сегментировать хапи-приложения в небольших портативных модулях. Почти все, что вы можете сделать с объектом hapi-сервера, вы можете сделать с помощью плагина. Вы можете добавлять маршруты, точки расширения, прослушивать события, создавать сегменты кэша; даже зарегистрировать механизм просмотра, уникальный из основного объекта сервера. Для получения дополнительной информации о плагинах, ознакомьтесь с руководством на hapijs.com.

Для этого примера мы будем использовать плагины bell и hapi-auth-cookie .

колокол

Bell — это хапи-плагин, созданный для обработки большинства утомительных рукопожатий, необходимых для интеграции со сторонними OAuth-провайдерами. Он поставляется со встроенной поддержкой наиболее часто используемых клиентов OAuth (Facebook, Twitter, GitHub и Google, и многие другие). Это означает, что большая часть тяжелой работы по интеграции OAuth с GitHub уже выполнена. Нам просто нужно настроить наш сервер hapi для его использования.

Белл обрабатывает все назад и вперед, требуемые OAuth, и будет вызывать связанную функцию обработчика hapi только после успешной аутентификации пользователя. В противном случае хапи ответит 401. Одна вещь, на которую очень важно обратить внимание, состоит в том, что у звонка нет никакой концепции пользовательского сеанса. Это означает, что после аутентификации одного запроса через третью сторону эта аутентификация будет потеряна для последующих запросов. Вы можете использовать звонок для защиты всех ваших маршрутов, но тогда каждый отдельный запрос пользователей к вашему веб-сайту потребует танца OAuth, который будет крайне неэффективным. Нам нужен способ создать безопасный cookie, который содержит информацию о сеансе OAuth, и использовать этот безопасный cookie для аутентификации будущих запросов.

hapi-auth-cookie обеспечивает простое в использовании управление сеансами cookie. Пользователи должны быть аутентифицированы другим способом; Все, что делает hapi-auth-cookie — это предоставляет API для получения и установки зашифрованных файлов cookie. У него есть несколько других служебных функций, но важно понимать, что он не выполняет никакой аутентификации сам по себе.

hapi-auth-cookie расширяет объект request hapi, добавляя методы через request.auth.session ; в частности request.auth.session.set и request.auth.session.clear . set для создания безопасного сеанса cookie и clear чтобы удалить. Эти методы добавляются в точку расширения сервера onPreAuth.

Для нашего сервера Bell будет отвечать за все переговоры OAuth и, в случае успеха, использовать hapi-auth-cookie, чтобы установить зашифрованный cookie с request.auth.session.set .

Настройка плагинов

В следующем разделе кода мы собираемся заполнить пустую функцию register и настроить два плагина для нашего сервера, который мы начали на рисунке 1.

 var Hapi = require('hapi'); var Bell = require('bell'); var AuthCookie = require('hapi-auth-cookie'); //... refer to Listing 1 server.register([Bell, AuthCookie], function (err) { if (err) { console.error(err); return process.exit(1); } var authCookieOptions = { password: 'cookie-encryption-password', //Password used for encryption cookie: 'sitepoint-auth', // Name of cookie to set isSecure: false }; server.auth.strategy('site-point-cookie', 'cookie', authCookieOptions); var bellAuthOptions = { provider: 'github', password: 'github-encryption-password', //Password used for encryption clientId: 'huU4KjEpMK4TECW',//'YourAppId', clientSecret: 'aPywVjShm4aWub7eQ3ub3FbADvTvz9',//'YourAppSecret', isSecure: false }; server.auth.strategy('github-oauth', 'bell', bellAuthOptions); server.auth.default('site-point-cookie'); //... refer to Listing 1 

Перечисление 2, Настройка плагинов bell и hapi-auth-cookie

Код Объяснение

server.register — это точка входа для добавления плагинов на сервер хапи. Он поддерживает несколько различных сигнатур функций, но для наших нужд мы передадим массив объектов. Каждый объект должен реализовывать функцию register которая будет вызываться и предоставлять текущий объект сервера хапи. Как только все плагины будут зарегистрированы, обратный вызов будет выполнен.

Нам нужно сделать небольшой обход, чтобы объяснить, как хапи обрабатывает аутентификацию. Аутентификация с помощью хапи разбита на две концепции; схемы и стратегии. Документация, здесь описывает это лучше всего:

Думайте о схеме как об общем типе аутентификации, таком как «базовый» или «дайджест». Стратегия, с другой стороны, является предварительно настроенным и именованным экземпляром схемы.

Помимо очень специфических и сложных ситуаций, вы будете использовать заранее созданные схемы и настраивать конкретную стратегию, подходящую для вашего приложения. Стратегия аутентификации будет использоваться во всем приложении для защиты ресурсов и является «экземпляром» схемы; схема является средством аутентификации запросов. И bell, и hapi-auth-cookie регистрируют новые схемы через server.auth.scheme ; схемы «колокол» и «печенье».

Имя схемы является вторым параметром для server.auth.strategy . Схема должна быть зарегистрирована на сервере хапи перед регистрацией стратегий, которые ее используют. Вот почему нам нужно сначала зарегистрировать плагины, а затем настроить стратегии через server.auth.strategy .

В листинге 2 мы сначала регистрируем стратегию «cookie» и называем ее «site-point-cookie». Во всем коде мы будем ссылаться на «site-point-cookie», чтобы ссылаться на эту настроенную стратегию cookie. Полное объяснение всех доступных опций можно найти здесь . В нашем примере мы используем только password , cookie и isSecure . password должен быть надежной строкой, потому что он будет использоваться модулем железа для шифрования и дешифрования куки. cookie — это имя cookie, и isSecure устанавливает isSecure «Secure» в результирующем заголовке Set-Cookie. Это означает, что этот файл cookie будет передаваться только через HTTPS-соединения. На данный момент мы устанавливаем значение false чтобы упростить использование этого примера, но в целом это должно быть установлено в значение true .

GitHub-OAuth

Вторая и более интересная стратегия — это тип колокольчика github-oauth. Подобно регистрации «site-point-cookie», мы передаем имя, схему и объект параметров. Полный список вариантов стратегии звонка можно найти здесь . provider установлен на «GitHub», потому что Bell имеет встроенную поддержку интеграции GitHub OAuth. Он также может быть установлен на объект, если вы пытаетесь интегрироваться с поставщиком, неизвестным для звонка. password — это строка, используемая для шифрования временного файла cookie на этапах авторизации протокола. Этот файл cookie сохраняется только на этапах авторизации, после чего он уничтожается. clientId и clientSecret — это значения, которые мы создали в разделе «Начало работы». Значения в листинге 2 не будут работать, так как они являются просто случайным бредом для этого примера, вам нужно будет вставить свои собственные значения в код. Наконец, isSecure выполняет ту же функцию, что и в ‘site-point-cookie’.

Наконец, мы устанавливаем аутентификацию по умолчанию для всего сервера, чтобы использовать нашу стратегию cookie под названием «site-point-cookie». Это просто удобная настройка. Он сообщает hapi аутентифицировать запрос с помощью стратегии ‘site-point-cookie’ для каждого маршрута, добавленного с server.route . Это значительно уменьшает количество дублированных параметров конфигурации, необходимых для каждого маршрута.

Заставить это работать

Мы наконец-то закончили со всей конфигурацией и настройкой! Все, что остается, — это несколько строк логики, чтобы соединить все воедино. Как только вы увидите необходимый объем кода, вы увидите, что hapi действительно является инфраструктурой, ориентированной на конфигурацию. Давайте пройдемся по каждому из маршрутов в листинге 1 и обновим объект конфигурации и обработчик для работы.

войти в маршрут

Маршрут входа в систему — это маршрут, к которому нужно обратиться и выполнить танец OAuth с сервером GitHub. В листинге 3 показана обновленная опция конфигурации маршрута:

 method: 'GET', path: '/login', config: { auth: 'github-oauth', handler: function (request, reply) { if (request.auth.isAuthenticated) { request.auth.session.set(request.auth.credentials); return reply('Hello ' + request.auth.credentials.profile.displayName); } reply('Not logged in...').code(401); } } 

Перечисление 3 Вход в обновления маршрута

Только опция config изменилась здесь. Во-первых, мы хотим установить опцию auth ‘github-oauth’. Это значение относится к нашей стратегии «звонка», которую мы создали в листинге 2 под названием «github-oauth». Это говорит хапи использовать стратегию github-oauth при попытке аутентификации этого маршрута. Если мы опускаем эту опцию, hapi отступит и будет использовать стратегию по умолчанию, указанную в листинге 2; «Сайт-точка-печенье. Полный список доступных параметров auth выходит за рамки этой статьи, но вы можете прочитать о них здесь .

В функции-обработчике мы проверяем значение request.auth.isAuthenticated запроса. request.auth добавляется в request только на маршрутах с включенной аутентификацией. Если isAuthenticated имеет значение true, мы хотим установить cookie, указывающий на это. Помните, hapi-auth-cookie добавил объект session в request.auth с функциями set и clear . Итак, теперь, когда пользователь прошел проверку подлинности с помощью GitHub, мы хотим создать файл cookie сеанса для использования в приложении с request.auth.session.set и передать объект учетных данных, возвращенный нам из GitHub. Это создаст зашифрованный cookie с именем ‘sitepoint-auth’ в соответствии с параметрами, которые мы передали в hapi-auth-cookie. Наконец, мы хотим ответить небольшим сообщением с отображаемым именем GitHub.

Если пользователь не авторизован или отказывает в доступе к GitHub OAuth, мы ответим сообщением и кодом состояния 401.

маршрут аккаунта

Маршрут учетной записи должен отображать информацию пользователей GitHub, если они вошли в систему, а если нет, ответьте 401. Обновленный код конфигурации и обработчика приведен ниже в листинге 4.

 method: 'GET', path: '/account', config: { handler: function (request, reply) { reply(request.auth.credentials.profile); } } 

Обновления маршрута учетной записи перечисления 4

Не так много изменений в этом маршруте. Поскольку мы не переопределяли ни одно из значений auth в объекте config , этот маршрут использует стратегию cookie по умолчанию. Когда запрашивается маршрут учетной записи, hapi ищет файл cookie «sitepoint-auth» и проверяет, существует ли он и является ли он допустимым файлом cookie для этого запроса. Если это так, будет вызван обработчик, в противном случае ответом будет 401. request.auth.credentials — это значение cookie, которое мы установили в журнале в маршруте в листинге 3, а в profile GitHub хранит большую часть информации учетной записи пользователя. ,

На этом этапе вы сможете протестировать два добавленных нами маршрута («/ login» и «/ account») и посмотреть, как они работают вместе и как они реагируют.

домашний маршрут

Как и большинство веб-сайтов, у нас должен быть маршрут в корне сайта. Оглядываясь назад на то, что мы хотим, чтобы этот маршрут делал, ответ должен быть адаптирован в зависимости от состояния аутентификации пользователей. Пользователь не должен получать 401, если он не вошел в систему, вместо этого он должен увидеть ненастроенную домашнюю страницу. Если они вошли в систему, мы хотим приветствовать их обратно с настроенным сообщением.

 method: 'GET', path: '/', config: { auth: { mode: 'optional' }, handler: function (request, reply) { if (request.auth.isAuthenticated) { return reply('welcome back ' + request.auth.credentials.profile.displayName); } reply('hello stranger!'); } } 

Обновления маршрута дома перечисления 5

В листинге 5 представлена ​​новая концепция настройки auth ; mode Значение mode может принимать одно из трех строковых значений; ‘обязательный’, ‘необязательный’ и ‘попробовать’. «Требуется» означает, что запрос должен иметь настоящую и действительную аутентификацию. «опционально» означает, что запрос не должен иметь аутентификацию, но если он есть, он должен быть действительным. Наконец, ‘try’ — это то же самое, что и ‘option’, но аутентификация не обязательно должна быть действительной.

Этот маршрут имеет стратегию cookie по умолчанию, которую мы настроили в листинге 2, поэтому все, что нам нужно сделать, это установить mode и strategy будет «site-point-cookie». В обработчике мы можем проверить состояние auth запроса, аналогичного листингу 3. Если это правда, у пользователя есть действительный файл cookie sitepoint-auth, и мы можем ответить на запрос информацией, хранящейся в request.auth.credentials ; как в листинге 4. Если статус auth равен false, мы ничего не знаем о пользователе, функция-обработчик ответит общим сообщением. Попробуйте изменить mode на «обязательный» и очистить куки, чтобы увидеть разницу между «обязательным» и «необязательным».

маршрут выхода

Наконец, давайте обновим маршрут выхода, чтобы удалить куки сессии.

 method: 'GET', path: '/logout', config: { auth: false, handler: function (request, reply) { request.auth.session.clear(); reply.redirect('/'); } } 

Обновления маршрута выхода из перечисления 6

Поскольку у нас есть стратегия аутентификации по умолчанию для всех маршрутов, мы хотим отключить auth для этого маршрута, чтобы пропустить любой запрос. Это полезно запомнить, если вы используете стратегию по умолчанию. В противном случае вы закончите аутентификацию каждого отдельного запроса к вашему серверу, и вы, вероятно, не захотите этого; особенно для статических ресурсов. В обработчике мы вызываем request.auth.session.clear() который удаляет cookie «sitepoint-auth», и, наконец, мы перенаправляем пользователя обратно в корень сайта. Если у пользователя нет cookie-файла «sitepoint-auth», этот код по сути является «запретным», но ничего не повредит и совершенно безопасен.

Резюме

Это кажется большим количеством слов, но большинство объясняет параметры конфигурации и то, как работают некоторые из внутренних компонентов аутентификации hapi. хапи разбивает аутентификацию на две концепции; схемы и стратегии. Схема — это общий тип аутентификации, а стратегия — это настроенный экземпляр схемы. Мы использовали bell для выполнения танца OAuth с GitHub, и мы использовали hapi-auth-cookie для сохранения информации GitHub пользователя в зашифрованный cookie с именем «sitepoint-auth». Мы использовали этот файл cookie в остальной части приложения для определения статуса аутентификации.

Большая часть кода в реальных обработчиках маршрутов чрезвычайно тривиальна, потому что большая часть тяжелой работы выполняется с помощью плагинов хапи. В маршруте входа в систему мы устанавливаем безопасный cookie, содержащий всю информацию, отправляемую с GitHub. В ресурсе учетной записи текущее содержимое файла cookie отправляется обратно пользователю как JSON. На домашнем маршруте мы изменили mode аутентификации, чтобы разрешить сочетание отсутствия аутентификации и аутентификации, что является очень распространенным сценарием для корневых ресурсов, и ответили соответствующим образом. Наконец, мы полностью отключили аутентификацию для маршрута выхода из системы, очистили файл cookie sitepoint-auth и перенаправили пользователя на домашнюю страницу.

Надеемся, что после прочтения этой статьи вы увидите, что большая часть необходимой работы выполняется только в конфигурации. За базовым шаблоном hapi очень мало кода. Я рекомендую вам проверить полный рабочий код и самостоятельно поэкспериментировать с различными параметрами и параметрами аутентификации.


Если вы хотите узнать больше о Hapi.js, посмотрите образец видео из нашего мини-курса Build Plugins with Hapi.js. В этом курсе вы познакомитесь с основами Hapi с помощью серии видеороликов, посвященных маршрутизации, представлениям, жизненному циклу запросов и мощной системе плагинов Hapi.