Skip to content

Instantly share code, notes, and snippets.

@GregBaugues
Last active July 11, 2022 02:21
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save GregBaugues/276d7c13e9f7a28db010 to your computer and use it in GitHub Desktop.
Save GregBaugues/276d7c13e9f7a28db010 to your computer and use it in GitHub Desktop.
Google API OAuth 2.0 refresh token (Ruby on Rails)
# The OAuth access token provided by the Google API expires in 60 minutes. After expiration,
# you must exchange a refresh token for a new access token. Unfortunately, the the Google API
# ruby gem does not include a method for refreshing access tokens.
# You can read up on how to refresh an access token here:
# https://developers.google.com/accounts/docs/OAuth2WebServer#refresh
# This Token model implements that process. It's based off of a Token model that can be created
# by running:
# rails g model Token token:text refresh_token:string expires_at:datetime
# This code assumes you Google API CLIENT_ID and CLIENT_SECRET are set as environment variables.
# Google only sends the refresh token the first time you authorize your account.
# If you didn't save it, go to your Gmail Account Permissions and revoke permissions to your app
class Token < ActiveRecord::Base
def to_params
{ 'refresh_token' => refresh_token,
'client_id' => ENV['CLIENT_ID'],
'client_secret' => ENV['CLIENT_SECRET'],
'grant_type' => 'refresh_token'}
end
def request_token_from_google
url = URI("https://accounts.google.com/o/oauth2/token")
Net::HTTP.post_form(url, self.to_params)
end
def refresh!
data = JSON.parse(request_token_from_google.body)
update_attributes(
token: data['access_token'],
expires_at: Time.now + data['expires_in'].to_i.seconds
)
end
def self.access_token
#convenience method to retrieve the latest token and refresh if necessary
t = Token.last
t.refresh! if t.expires_at < Time.now
t.token
end
end
@adellhk
Copy link

adellhk commented Dec 13, 2015

Thanks for putting this up @GregBaugues 👌

I ended up with a slightly simpler/less elegant implementation which I have within a rake task so that I can run it as an hourly cron job:

# app/models/google_play_token.rb

class GooglePlayToken < ActiveRecord::Base
    validates :access_token, :refresh_token, :expires_at, presence: true

    def refresh!
        refresh_token! if expires_at < Time.now
    end

    private

    def refresh_token!
        response = HTTParty.post("https://accounts.google.com/o/oauth2/token",
            body: {
                grant_type: "refresh_token",
                client_id: ENV['GOOGLE_PLAY_CLIENT_ID'],
                client_secret: ENV['GOOGLE_PLAY_CLIENT_SECRET'],
                refresh_token: refresh_token
                })
        response = JSON.parse(response.body)
        update_attributes(
            access_token: response["access_token"],
            expires_at: Time.now + response["expires_in"].to_i.seconds
            )
    end
end

# lib/tasks/google_play_token.rake

namespace :google_play_token do
    desc "Refresh google play access token"
    task :refresh => :environment do
        puts "Refreshing..."
        GooglePlayToken.first.refresh!
        puts "Done"
    end

end

@walshyb
Copy link

walshyb commented Jul 18, 2016

THANK YOU SO MUCH! You don't know how long I've been looking for where/when Google sends refresh token. You think that it'd be in the docs, but it's not.

@mateisuica
Copy link

great!

@vedala
Copy link

vedala commented Mar 24, 2017

Thank you both GregBaugues, adellhk. I found this after spending a lot of time reading Google API docs, saved me a lot of time.

@ajsharp
Copy link

ajsharp commented Oct 19, 2017

Google only sends the refresh token the first time you authorize your account.

Thanks for including this, super helpful.

@pondkrub
Copy link

pondkrub commented Jun 4, 2019

Thanks!!

@emcmanus
Copy link

emcmanus commented Oct 3, 2021

Net::HTTP does not raise exceptions on HTTP failures (it just returns one of like 40 different error classes).

So when Google returns an error, which it occasionally does, response.body will be something like "{\n \"error\": \"\",\n \"error_description\": \"\"\n}".

This is valid JSON, so it will parse and evaluate to update_attributes(access_token: nil, expires_at: Time.now). Even if your refresh token is valid.

This is probably not what you want, so use a sane library, like RestClient, which will raise an exception instead:

  def request_token_from_google
    RestClient.post("https://accounts.google.com/o/oauth2/token", self.to_params)
  end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment