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
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.
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
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.
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.
There are several reasons why a refresh token would stop working.
It gets to old refresh tokens expire after six months if not used.
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.
the user can revoke your access.
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)
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
My question is: How to get the token Adwords without needing to interact with the browser, but only via code
Now I run this code:
token = adwords.authorize () do | auth_url |
puts "Hit Auth error, please navigate to URL: \ n \ t% s"% auth_url
print 'log in and type the verification code:'
VERIFICATION_CODE = gets.chomp
VERIFICATION_CODE
end
And lap the token that is returned in browser url to liberate access.
Is to do all this using only Ruby code?
Thank you.
In order to get offline access to a user's AdWords data,
They must grant your app offline permissions
You must store and use a refresh token in order to get new access tokens after the initial one expires.
Asking for Offline Permissions
To ask for offline permissions, simply add the following query parameter to your OAuth2 URL:
&access_type=offline
Here is an example of a full URL requesting offline permission to the user's AdWords data:
https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=auto&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_CALLBACK_URL&response_type=code&scope=https%3A%2F%2Fadwords.google.com%2Fapi%2Fadwords%2F
Your client ID will be something like this: 111122223333.apps.googleusercontent.com
Make sure to properly escape your callback URL before substituting it in.
Using a Refresh Token
This way, when you get an access_token for the account, you will also get a refresh_token from Google in the same response. Store this in your database with the rest of the account's data (including the access_token).
Later, when your original access token has expired, you can request a new one by making the following request:
https://accounts.google.com/o/oauth2/token?refresh_token=THE_REFRESH_TOKEN&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=refresh_token
I'm working on an application that integrates with GitHub and am having issues "logging out" a user that was previously authenticated. When I attempt to revoke the authorization token for the user, I get a 404 Not Found response from the API.
According to the documentation, it looks like I should just be able to make a DELETE request to https://api.github.com/authorizations/[authTokenId]. I have tried a couple of different things including:
Ensuring the Authorization header is set with the current auth token
Ensuring the UserAgent header is set with what I use for the rest of the API calls
Nothing seems to result in anything but a 404 though. I have validated that the token is valid and has that the Id matches with what is expected (id property from the authorization response and from the "check an authorization" response as well). Anyone have another thought on something I could be missing?
Looks like currently you need to include a basic authentication header (including a base64 encoded string of your username/password).
Not ideal for my purposes since I want to revoke the token when a user "logs out" of my application and I don't want to store their username/password. I've sent GitHub support an email about it to see if they have any other ideas.
Update 6/12/2013
GitHub support has stated that the above is expected at this juncture, but they are considering updating to allow revoking an authorization using the authorization as the means of authentication.
For now I'm going to require the user to enter their username/password a second time to revoke the authorization.
I have a Rails (3.2.11) application that allows users to post updates to their LinkedIn profiles. I'm currently using the omniauth-linkedin gem to capture the initial user authentication and the linkedin gem to post the updates. The problem I'm having is that LinkedIn access tokens expire after 60 days, but according to their documentation a token can be refreshed prior to expiration without a user having to reauthorize the application.
I've looked at the LinkedIn Tips and Tricks, Authentication Overview, and tons of posts on StackOverflow - this, this, and this being just a couple of examples - and I still can't find any answers.
After a user authorizes the app (via omniauth-linkedin), I save the access_token and secret returned to me from LinkedIn. I need to figure out how I can use the still-valid access_token to refresh it and extend the expiration date another 60 days.
I've tried using the authenticate endpoint from LinkedIn (where tokens.access_token is the currently valid token):
url = "https//www.linkedin.com/uas/oauth/authenticate?oauth_token=" + tokens.access_token
result = RestClient.post(url, {oauth_callback: "http://localhost:3000/users/auth/linkedin/callback"})
but I get an undefined method 'request_uri' for #<URI::Generic:0x1b144d20> Exception.
I've tried using the OAuth::Consumer client (where tokens.access_token and tokens.token_secret are the currently valid tokens):
configuration = { site: 'https://api.linkedin.com', authorize_path: '/uas/oauth/authenticate',
request_token_path: '/uas/oauth/requestToken', access_token_path: '/uas/oauth/accessToken' }
consumer = OAuth::Consumer.new(ENV['LINKEDIN_APP_ID'], ENV['LINKEDIN_SECRET'], configuration)
access_token = OAuth::AccessToken.new(consumer, tokens.access_token, tokens.token_secret)
but this just gives me back the same access_token and secret.
In the end, I'd love to be able to leverage the existing omniauth-linkedin gem functionality to handle this refresh, any idea if this is possible? Thanks!
In your second approach (using the OAuth::Consumer client and passing in your existing access token and secret) should refresh the token for you. As the documentation states, as long as the current user is logged into LinkedIn.com and the current access token hasn't expired yet, the token will be refreshed.
That doesn't mean necessarily that you'll get a new token. You may get the same one as you had before. The key difference is that the lifespan of the token should 60 days. You can verify this by check the value of oauth_expires_in parameter. It should be set to 5184000.
This blog post goes into detail about refreshing the token: https://developer.linkedin.com/blog/tips-and-tricks-refreshing-access-token
Following http://code.google.com/apis/gmail/oauth/protocol.html#imap I'm trying to implement an IMAP client for Gmail that uses OAuth.
My code works fine and I can connect to the IMAP server, however, after 1 day when I retry using the same XOAUTH value, I get an invalid credentials.
It might be a problem in my code causing this, but I need to know if I can pass the same XOAUTH value to the IMAP AUTHENTICATE method everytime or do I need to regenerate it just before trying to access?
My understanding was that once I have the Access Token and Token Secrets back I can use them to generate the XOAUTH value once and then keep using it.
Using the reliable method of trial and error I figured although access token and secret are long living but the XOAUTH value has to be generated for IMAP every time.