32

I am using omniauth-oauth2 in rails to authenticate to a site which supports oauth2. After doing the oauth dance, the site gives me the following, which I then persist into the database:

  1. Access Token
  2. Expires_AT (ticks)
  3. Refresh token

Is there an omniauth method to refresh the token automatically after it expires or should I write custom code which to do the same?

If custom code is to be written, is a helper the right place to write the logic?

6 Answers 6

26

Omniauth doesn't offer this functionality out of the box so i used the previous answer and another SO answer to write the code in my model 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

And before making the API call using the access token, you can call the method like this where current_user is the signed in user.

current_user.refresh_token_if_expired

Make sure to install the rest-client gem and add the require directive require 'rest-client' in the model file. The ENV['DOMAIN'], ENV['APP_ID'] and ENV['APP_SECRET'] are environment variables that can be set in config/environments/production.rb (or development)

Sign up to request clarification or add additional context in comments.

1 Comment

How ENV['DOMAIN'] should be look like?
24

In fact, the omniauth-oauth2 gem and its dependency, oauth2, both have some refresh logic built in.

See in 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

And in https://github.com/intridea/omniauth-oauth2/blob/master/lib/omniauth/strategies/oauth2.rb#L74 :

self.access_token = access_token.refresh! if access_token.expired?

So you may not be able to do it directly with omniauth-oauth2, but you can certainly do something along the lines of this with 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 Comments

Is there an easy way to load the client from an existing strategy in a Rails app? I ended up declaring a new client just as the Oauth2 strategy does: github.com/intridea/omniauth-oauth2/blob/master/lib/omniauth/…
I guess your question is, how do I find a reference to the existing OmniAuth strategy? Once you have the strategy object, it has a client method. Here's other people asking about finding the strategies: stackoverflow.com/questions/13112430/…
Thanks @Eero - this led me to my fix below: stackoverflow.com/a/38276378/224707
8

Eero's answer unlocked a path for me to solve this. I have a helper concern for my classes which get me a GmailService. As part of this process, the user object (which contains the google auth info) gets checked if it's expired. If it has, it refreshes before returning the service.

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

1 Comment

OmniAuth::Strategies::YOUR_PROVIDER(nil, client_id, secret)... is this the only way to access to strategies, and then their client?
5
+25

There is some information here, too much to list here. It may depend on the provider you are using, and their allowed usage of the refresh-token

Comments

0

Similarly to other answers I followed this approach, where the model storing the auth and refresh tokens is used, abstracting API interactions from that logic.

See https://stackoverflow.com/a/51041855/1392282

Comments

0

If you are using devise you can create a new strategy the following way I guess, so that you don't need to repeat client id and secret everywhere:

# first argument is something called app, but not sure what but nil seems to be fine.
Strategies::MyStrategy.new(nil, *Devise.omniauth_configs[:mystrategy].args)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.