Подтвердить что ты не робот

Обновить токен, используя приложение Omniauth-oauth2 в Rails

Я использую omniauth-oauth2 в рельсах для аутентификации на сайте, который поддерживает oauth2. После выполнения oauth dance сайт дает мне следующее, которое я затем сохраняю в базе данных:

  • Ток доступа
  • Expires_AT (тики)
  • Обновить токен

Есть ли способ omniauth для обновления маркера автоматически после его истечения или я должен написать собственный код, который будет делать то же самое?

Если пользовательский код должен быть записан, является ли помощником правильное место для записи логики?

4b9b3361

Ответ 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