Обновить токен, используя приложение Omniauth-oauth2 в Rails
Я использую omniauth-oauth2 в рельсах для аутентификации на сайте, который поддерживает oauth2. После выполнения oauth dance сайт дает мне следующее, которое я затем сохраняю в базе данных:
- Ток доступа
- Expires_AT (тики)
- Обновить токен
Есть ли способ omniauth для обновления маркера автоматически после его истечения или я должен написать собственный код, который будет делать то же самое?
Если пользовательский код должен быть записан, является ли помощником правильное место для записи логики?
Ответы
Ответ 1
Omniauth не предлагает эту функциональность из коробки, поэтому я использовал предыдущий ответ и еще один ответ SO, чтобы написать код в моей модели User.rb
def refresh_token_if_expired
if token_expired?
response = RestClient.post "#{ENV['DOMAIN']}oauth2/token", :grant_type => 'refresh_token', :refresh_token => self.refresh_token, :client_id => ENV['APP_ID'], :client_secret => ENV['APP_SECRET']
refreshhash = JSON.parse(response.body)
token_will_change!
expiresat_will_change!
self.token = refreshhash['access_token']
self.expiresat = DateTime.now + refreshhash["expires_in"].to_i.seconds
self.save
puts 'Saved'
end
end
def token_expired?
expiry = Time.at(self.expiresat)
return true if expiry < Time.now # expired token, so we should quickly return
token_expires_at = expiry
save if changed?
false # token not expired. :D
end
И перед вызовом API с использованием токена доступа вы можете вызвать метод, подобный этому, где current_user является подписанным пользователем.
current_user.refresh_token_if_expired
Обязательно установите драгоценный камень rest-client и добавьте требуемую директиву require 'rest-client'
в файл модели. ENV['DOMAIN']
, ENV['APP_ID']
и ENV['APP_SECRET']
являются переменными окружения, которые могут быть установлены в config/environments/production.rb
(или развитии)
Ответ 2
Фактически omniauth-oauth2 gem и его зависимость oauth2, оба имеют встроенную логику обновления.
См. в https://github.com/intridea/oauth2/blob/master/lib/oauth2/access_token.rb#L80
# Refreshes the current Access Token
#
# @return [AccessToken] a new AccessToken
# @note options should be carried over to the new AccessToken
def refresh!(params = {})
fail('A refresh_token is not available') unless refresh_token
params.merge!(:client_id => @client.id,
:client_secret => @client.secret,
:grant_type => 'refresh_token',
:refresh_token => refresh_token)
new_token = @client.get_token(params)
new_token.options = options
new_token.refresh_token = refresh_token unless new_token.refresh_token
new_token
end
И в https://github.com/intridea/omniauth-oauth2/blob/master/lib/omniauth/strategies/oauth2.rb#L74:
self.access_token = access_token.refresh! if access_token.expired?
Таким образом, вы не сможете сделать это напрямую с omniauth-oauth2, но вы, безусловно, можете что-то сделать в этом направлении с помощью oauth2:
client = strategy.client # from your omniauth oauth2 strategy
token = OAuth2::AccessToken.from_hash client, record.to_hash
# or
token = OAuth2::AccessToken.new client, token, {expires_at: 123456789, refresh_token: "123"}
token.refresh!
Ответ 3
Eero ответ открыл путь для меня, чтобы решить эту проблему. У меня есть вспомогательная забота о моих классах, которые дают мне GmailService. В рамках этого процесса пользовательский объект (который содержит информацию об аутентификации google) проверяется, если он истек. Если он есть, он обновляется перед возвратом службы.
def gmail_service(user)
mail = Google::Apis::GmailV1::GmailService.new
# Is the users token expired?
if user.google_token_expire.to_datetime.past?
oauth = OmniAuth::Strategies::GoogleOauth2.new(
nil, # App - nil seems to be ok?!
"XXXXXXXXXX.apps.googleusercontent.com", # Client ID
"ABC123456" # Client Secret
)
token = OAuth2::AccessToken.new(
oauth.client,
user.google_access_token,
{ refresh_token: user.google_refresh_token }
)
new_token = token.refresh!
if new_token.present?
user.update(
google_access_token: new_token.token,
google_token_expire: Time.at(new_token.expires_at),
google_refresh_token: new_token.refresh_token
)
else
puts("DAMN - DIDN'T WORK!")
end
end
mail.authorization = user.google_access_token
mail
end
Ответ 4
Здесь есть некоторая информация, слишком много, чтобы перечислить здесь. Это может зависеть от поставщика, которого вы используете, и их разрешенного использования refresh-token
Ответ 5
Подобно другим ответам, я следовал этому подходу, где используется модель хранения токенов аутентификации и обновления, абстрагируя взаимодействия API от этой логики.
См. fooobar.com/questions/2342249/...
Ответ 6
Мальчик, это было сложнее, чем следовало бы решить.
Вот полная, работающая реализация, которую мы используем для предоставления авторизации google-api-client
, который использует гемы Devise, Omniauth и OAuth2. Класс GoogleAuthToken
заключает в себе знания о том, как извлекать всевозможную информацию, обновлять себя по мере необходимости и предоставлять маркер доступа, который можно использовать в качестве авторизации для google-api-client
.
# Using the OAuth2 gem for auth in 'google-api-client' is not quite enough, and requires a mixin
# for the 'apply!' method.
# https://github.com/googleapis/google-api-ruby-client/issues/296#issuecomment-147545845
module GoogleAuthorizationOAuth2AccessTokenPatch
def apply!(headers)
headers['Authorization'] = "Bearer #{token}"
end
end
class GoogleAuthToken < ApplicationRecord
OMNIAUTH_CONFIG_NAME = :google_oauth2
OAuth2::AccessToken.include GoogleAuthorizationOAuth2AccessTokenPatch
def self.from_omniauth(omniauth)
provider, uid, info, credentials = omniauth.provider, omniauth.uid, omniauth.info, omniauth.credentials
GoogleAuthToken.new(
uid: uid,
provider: provider,
name: info.name,
email: info.email,
image: info.image,
token: credentials.token,
refresh_token: credentials.refresh_token,
expires_at: credentials.expires_at,
)
end
def access_token
return @access_token if @access_token.present? && [email protected]_token.expired?
@access_token = build_or_refresh_access_token
end
private def build_or_refresh_access_token
client_id = Devise.omniauth_configs[OMNIAUTH_CONFIG_NAME].strategy.client_id
client_secret = Devise.omniauth_configs[OMNIAUTH_CONFIG_NAME].strategy.client_secret
client = Devise.omniauth_configs[OMNIAUTH_CONFIG_NAME].strategy_class.new(nil, client_id, client_secret).client
access_token = OAuth2::AccessToken.new(client, token, refresh_token: refresh_token, expires_at: expires_at)
if access_token.expired?
access_token = access_token.refresh!
update!(token: access_token.token, refresh_token: access_token.refresh_token, expires_at: access_token.expires_at)
end
access_token
end
end
# USAGE
token = GoogleAuthToken.from_omniauth(request.env['omniauth.auth'])
token.save!
client = Google::Apis::CalendarV3::CalendarService.new
client.authorization = token.access_token
client.list_calendar_lists # or whatever