Google::Apis::AuthorizationError (Unauthorized)
Asked Answered
B

6

20

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

Borman answered 25/5, 2018 at 6:46 Comment(6)
What is the SCOPE you are sending? Do you have a minimal repo to reproduce the issue?Berkey
Have you tried to catch the error and use auth.fetch_access_token and then trying again? This may happen when user has changed recently password and access to gmail api is revoked automatically by Google api.Beetlebrowed
@TarunLalwani Please check the following repo github.com/MeenakshiNaik/oauth2sample. In this repo, all ouath code is thereBorman
@Beetlebrowed Yes I tried using auth.fetch_access_token but same issue is occurring with new access_token too which i get from auth.fetch_access_tokeBorman
Does the token get expired/revoked only if you use it, or does the very first request fail too if you wait some time before sending it?Epirus
@Borman I am not sure whether 'client.update' method can help but you can give a try, as mentioned in rubydoc.info/github/google/signet/Signet/OAuth2/ClientEnscroll
I
2

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.

Izolaiztaccihuatl answered 6/6, 2018 at 7:0 Comment(1)
can add few more code .. like how are you using it with google_api_client gem api .. when do you fetch the a new access_token (do you do it always) or when it is about to expire. May I can take some pointer from there.Borman
O
1

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
Overlooker answered 7/6, 2018 at 19:29 Comment(0)
F
1

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.

Fungible answered 8/6, 2018 at 1:55 Comment(2)
Please check repo, all oauth latest code is there: github.com/MeenakshiNaik/oauth2sampleBorman
@Borman the repo is different code than the post. GmailService#authorization= takes a Signet::OAuth2::Client not an AccessToken, so that is part of your problem. It still appears you are also needlessly refreshing the access token. Plus there is a syntax error (missing parenthesis) in the repo.Fungible
F
0

The thought that the refresh token will never expire is actually a misunderstanding. The actual scene is that the server issues a short-lived access token and a long lived refresh token. So in reality what happens is that the access token can be regained using the long lived refresh tokens but yes, you will have to request a new refresh token (as it expires too !). For example; you may treat refresh tokens as if they never expire. However on sign-in check for a new one, in case the user revokes the refresh token, in this scenario, Google will provide a new refresh token on sign-in so just update the refresh token.

Now the condition can be that the user revokes access to your application. In this case, the refresh token will expire (or I should actually say that it would become an unauthorized one). So if that is the scenario in your case, you will have to think on avoiding the revoking of access for the application.

For better understanding of it, you may refer to this document and even OAuth 2.0 documentation.

Finally answered 6/6, 2018 at 4:25 Comment(0)
C
0

There are several reasons why a refresh token would stop working.

  1. It gets to old refresh tokens expire after six months if not used.
  2. A user can reauthecate your application and get a new refresh token both refresh tokens will work you can have a max of fifty outstanding refresh tokens then the first will stop working.
  3. the user can revoke your access.
  4. Wakey daylight savings time bug of 2015. (we wont talk about that)

Gmail and reset password.

This is mostly like due to a password reset. OAuth grants with the gmail scopes are revoked when a user changes their password.

See Automatic OAuth 2.0 token revocation upon password change

In general, users can revoke grants at any time. You should be able to handle that case gracefully and alert the user that they need to reauthorize if they wish to continue using the functionality provided.

You have been doing a lot of testing i would guess are you saving the newest refresh token? If not then you may be using old refresh tokens and the will stop working. (number 2)

Conte answered 7/6, 2018 at 10:36 Comment(0)
C
-1

In my case, only youtube upload api raise

Unauthorized (Google::Apis::AuthorizationError)

and other api, like list videos api work well

it's because i use new google account and have not up video

i manually up video in youtube web, youtube require me create "channel"

and I try youtube up api again, it work

I guess it's because youtube has channel to up

Chairwoman answered 11/8, 2019 at 9:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.