users_controller.rb
[...] before_action :check_access_token [...] private def check_access_token redirect_to root_path unless session[:access_token] if session[:expires_at] <= Time.current req_params = "client_id=#{ENV['oauth_token']}&client_secret=#{ENV['oauth_secret']}&refresh_token=#{session[:refresh_token]}&grant_type=refresh_token&redirect_uri=#{ENV['oauth_redirect_uri']}" response = JSON.parse RestClient.post("#{ENV['server_base_url']}/oauth/token", req_params) set_oauth_info_from response end end
Если токен доступа не установлен, мы просто перенаправляем пользователя обратно. Если токен истек, мы генерируем новую строку с параметрами запроса client_id
, client_secret
, refresh_token
, redirect_uri
и grant_type
(для этого необходимо установить значение refresh_token
). Вы спросите, а как насчет сферы? По умолчанию новый токен доступа будет предоставлен для выполнения тех же действий, которые были первоначально одобрены пользователем. Вы можете сузить область, но вы не можете добавить области, которые не были предоставлены ранее.
Далее мы берем ответ и передаем его в метод set_oauth_info_from
который будет представлен сейчас:
application_controller.rb
[...] private def set_oauth_info_from(response) session[:access_token] = response['access_token'] session[:refresh_token] = response['refresh_token'] session[:expires_at] = response['expires_in'].seconds.from_now end
Используйте этот метод внутри SessionsController
чтобы избежать дублирования кода:
sessions_controller.rb
def create req_params = "client_id=#{ENV['oauth_token']}&client_secret=#{ENV['oauth_secret']}&code=#{params[:code]}&grant_type=authorization_code&redirect_uri=#{ENV['oauth_redirect_uri']}" response = JSON.parse RestClient.post("#{ENV['server_base_url']}/oauth/token", req_params) set_oauth_info_from response redirect_to root_path end
Это оно! Теперь наши пользователи могут без проблем получать токены обновления!
Создание собственного провайдера OmniAuth
Если ваше приложение однажды станет достаточно популярным, вы можете создать собственную стратегию OmniAuth и распространять ее как драгоценный камень. Похоже, что это можно сделать довольно легко . Конечно, мы не собираемся создавать отдельный гем, давайте сохраним нашу новую стратегию в папке lib клиентского приложения.
Библиотека / OmniAuth / стратегии / keepa.rb
require 'omniauth-oauth2' module OmniAuth module Strategies class Keepa < OmniAuth::Strategies::OAuth2 option :name, :keepa option :client_options, { :site => "http://localhost:3000", :authorize_url => "/oauth/authorize" } uid { raw_info["id"] } info do { :email => raw_info["email"] } end def raw_info @raw_info ||= access_token.get('/api/user').parsed end end end end
Я назвал свою стратегию Keepa, поэтому имя класса и option :name
должны быть установлены соответственно.
Метод raw_info
используется для получения информации о пользователе, который проходит аутентификацию с помощью нашей стратегии. /api/user
— это маршрут, который мы ввели в предыдущей статье. Напомню, как выглядит соответствующее действие:
апи / users_controller.rb
[...] def show render json: current_resource_owner.as_json end private def current_resource_owner User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token end [...]
Мы в основном выбираем пользователя, который инициировал фазу аутентификации, и отображаем его информацию в формате JSON.
uid { raw_info["id"] }
объясняет, какое поле использовать в качестве уникального идентификатора. Мы используем идентификатор из таблицы, но вместо этого вы можете ввести специальное поле.
info do { :email => raw_info["email"] } end
Это указывает, какие поля будут доступны внутри действия обратного вызова после успешной аутентификации. Вы можете добавить другие поля по мере необходимости.
Этот код зависит от абстрактной стратегии omniauth-oauth2 , поэтому подключите его:
Gemfile
[...] gem 'omniauth-oauth2', '1.3.1' [...]
и беги
$ bundle install
Я использую версию 1.3.1 здесь из-за этой проблемы, которая затрагивает Doorkeeper .
Теперь создайте новый файл инициализатора для клиентского приложения:
конфиг / Инициализаторы / omniauth.rb
require File.expand_path('lib/omniauth/strategies/keepa', Rails.root) Rails.application.config.middleware.use OmniAuth::Builder do provider :keepa, ENV['oauth_token'], ENV['oauth_secret'], scope: 'public write' end
Если вы когда-либо использовали OmniAuth в своих проектах, это должно быть очень знакомо. Единственное, что нужно отметить, это то, что нам нужно явно указать файл стратегии, поскольку он не загружается автоматически.
public
и write
— это области видимости, которые были представлены в предыдущей статье. В качестве разделителя следует использовать пробел.
OmniAuth указывает нам добавить специальный маршрут обратного вызова, поэтому замените старый:
конфиг / routes.rb
[...] get '/oauth/callback', to: 'sessions#create' [...]
с
конфиг / routes.rb
[...] get '/auth/:provider/callback', to: 'sessions#create' [...]
Изменить файл конфигурации:
конфиг / local_env.yml
[...] oauth_redirect_uri: 'http%3A%2F%2Flocalhost%3A3001%2Fauth%2Fkeepa%2Fcallback'
Также не забудьте открыть серверное приложение, перейти к localhost: 3000 / oauth / apps и отредактировать URI перенаправления для вашего приложения.
Измените главную страницу клиентского приложения:
просмотров / страниц / index.html.erb
[...] <% if session[:access_token] %> <%= link_to 'Get User', user_path %> <%= link_to 'Update User', update_user_path %> <% else %> <%= link_to 'Authorize via Keepa', '/auth/keepa' %> <% end %>
Следующим шагом является изменение действия обратного вызова:
sessions_controller.rb
[...] def create set_oauth_info_from request.env['omniauth.auth']['credentials'] redirect_to root_path end [...]
request.env['omniauth.auth']
должен быть вам знаком. Он содержит хэш аутентификации в следующем формате:
{"provider"=>:keepa, "uid"=>1, "info"=>{"email"=>"test@example.org"}, "credentials"=> {"token"=>"8d8...", "refresh_token"=>"321...", "expires_at"=>1453301961, "expires"=>true}, "extra"=>{}}
о {"provider"=>:keepa, "uid"=>1, "info"=>{"email"=>"test@example.org"}, "credentials"=> {"token"=>"8d8...", "refresh_token"=>"321...", "expires_at"=>1453301961, "expires"=>true}, "extra"=>{}}
Обратите внимание, что поле называется token
(не access_token
), и вместо expires_in
у нас есть expires_at
.
Поэтому метод set_oauth_info_from
должен быть немного изменен:
application_controller.rb
[...] def set_oauth_info_from(response) session[:access_token] = response['access_token'] || response['token'] session[:refresh_token] = response['refresh_token'] if response['expires_in'] session[:expires_at] = response['expires_in'].seconds.from_now else session[:expires_at] = Time.at response['expires_at'] end end [...]
Вспомогательный метод new_oauth_token_path
может быть полностью удален, и вы готовы аутентифицироваться с помощью вашей новой блестящей стратегии. Большой!
Дальнейшая настройка
Вы, вероятно, задаетесь вопросом, возможно ли изменить вид и маршрут Дверного дворника? И да — это вполне возможно.
Чтобы изменить представления , выполните эту простую команду:
$ rails generate doorkeeper:views
Он сгенерирует все виды и макеты, используемые Doorkeeper.
Вы можете использовать свои собственные макеты, настроив эти параметры в файле application.rb :
config.to_prepare do Doorkeeper::ApplicationsController.layout "my_layout" Doorkeeper::AuthorizationsController.layout "my_layout" Doorkeeper::AuthorizedApplicationsController.layout "my_layout" end
Маршруты и вспомогательные методы из вашего приложения можно использовать в представлениях Привратника, но они должны были иметь префикс main_app
(например, main_app.login_path
), потому что это решение является изолированным механизмом.
Настройка маршрутов по умолчанию также довольно проста. Метод use_doorkeeper
принимает блок со всеми необходимыми конфигурациями. Например, вы можете использовать собственный контроллер для управления приложениями OAuth 2:
use_doorkeeper do controllers :applications => 'custom_applications' end
controllers
принимают значения :authorizations
:tokens
:applications
и :authorized_applications
.
Вы можете даже пропустить некоторые контроллеры, сказав:
use_doorkeeper do skip_controllers :applications end
skip_controllers
принимает те же значения.
Обратитесь к этой странице вики для получения дополнительной информации.
Вывод
В этой статье мы доработали нашего собственного поставщика OAuth 2, немного его защитив, представив токены обновления и разработав собственную стратегию OmniAuth. Теперь вы готовы к дальнейшей настройке и расширению своего приложения по мере необходимости! Я настоятельно рекомендую вам просмотреть вики Doorkeeper, так как она содержит несколько хороших руководств по решению общих проблем.
Надеюсь, что эта серия статей была вам полезна и интересна. Если вы хотите, чтобы я затронул конкретную тему, не стесняйтесь спрашивать — я всегда с нетерпением жду ваших отзывов. Удачного кодирования и оставайтесь аутентифицированными!