Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google::Apis::AuthorizationError (Unauthorized)

We are creating an application with Ionic framework as front-end and Ruby on Rails as back-end. We are able to link Gmail account in our app. Account linking is working fine, we get serverAuthCode from front-end and then using that we get refresh token and we are able to fetch emails with that refresh token at first attempt. But within seconds, it get expired or revoked. Getting the following issue:

Signet::AuthorizationError (Authorization failed.  Server message:
{
  "error" : "invalid_grant",
  "error_description" : "Token has been expired or revoked."
})

It seems like, refresh token itself is expiring in seconds. Does anyone have any idea about how to fix it?

Update:

Existing code looks like this:

class User   
  def authentication(linked_account)
    client = Signet::OAuth2::Client.new(
    authorization_uri: 'https://accounts.google.com/o/oauth2/auth',
    token_credential_uri: Rails.application.secrets.token_credential_uri,
    client_id: Rails.application.secrets.google_client_id,
    client_secret: Rails.application.secrets.google_client_secret,
    scope: 'https://www.googleapis.com/auth/gmail.readonly, https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile',
    redirect_uri: Rails.application.secrets.redirect_uri,
    refresh_token: linked_account[:refresh_token]
  )

  client.update!(access_token: linked_account.token, expires_at: linked_account.expires_at)
  return  AccessToken.new(linked_account.token) unless client.expired?
  auth.fetch_access_token! 
 end

 def get_email(linked_account)
   auth = authentication(linked_account)
   gmail = Google::Apis::GmailV1::GmailService.new
   gmail.client_options.application_name = User::APPLICATION_NAME
   gmail.authorization = AccessToken.new(linked_account.token)
   query = "(is:inbox OR is:sent)"
   gmail.list_user_messages(linked_account[:uid], q: "#{query}")
   ## Getting error over here ^^
  end
end // class end 

class AccessToken
  attr_reader :token
  def initialize(token)
    @token = token
  end

  def apply!(headers)
    headers['Authorization'] = "Bearer #{@token}"
  end
end

Reference link: https://github.com/google/google-api-ruby-client/issues/296

like image 860
MeeSN Avatar asked May 25 '18 06:05

MeeSN


3 Answers

From what I can guess the issue seems to be on these two lines. The way token expiry is being checked and the new token is being generated. It would be great if there is minimal reproducible code.

return  AccessToken.new(linked_account.token) unless client.expired?
auth.fetch_access_token! 

Here is how I get my access token:

  def self.access_token(refresh_token)
    Cache.fetch(refresh_token, expires_in: 60.minutes) do
      url = GoogleService::TOKEN_CREDENTIAL_URI
      # p.s. TOKEN_CREDENTIAL_URI = 'https://www.googleapis.com/oauth2/v4/token'
      _, response = Request.post(
        url,
        payload: {
          "client_id": GoogleService::CLIENT_ID,
          "client_secret": GoogleService::CLIENT_SECRET,
          "refresh_token": refresh_token,
          "grant_type": "refresh_token"
        }
      )
      response['access_token']
    end
  end

And then use this access token for any purpose. Let me know how it goes and also if you are able to create a reproducible version of the API. That will be great.

like image 81
aks Avatar answered Nov 18 '22 01:11

aks


Have you tried refreshing the access token with the refresh token? You can catch the error and retry.

Something like this:

begin
  gmail.list_user_messages(linked_account[:uid], q: "#{query}")
rescue Google::Apis::AuthorizationError => exception
  client.refresh!
  retry
end
like image 45
gwcodes Avatar answered Nov 18 '22 00:11

gwcodes


Not enough code is posted, but what is posted looks wrong.

  • linked_account is not defined
  • Nowhere is it shown that linked_account.token is ever updated (or set, for that matter). It needs to be updated when the refresh_token is used to get a new access token.
  • auth appears to be undefined in the line auth.fetch_access_token!
  • GmailService#authorization= takes a Signet::OAuth2::Client not an AccessToken.

Probably what is happening is that you have a valid access token in linked_account.token until you call client.update!, which fetches a new access token and invalidates the old one. But since you never update linked_account, future calls fail until you go through the code path that resets it.

You only need to call client.update! if the access token has expired, and if it has expired and you get a new one, you need to store that new one in linked_account.token.

like image 1
Old Pro Avatar answered Nov 18 '22 01:11

Old Pro