Rails 4 / Koala: how to refresh long access token? - ruby-on-rails

Using Rails 4 and Koala gem version 2.
I have my personal Facebook page (not profile).
When I am publishing a new post in my website I want to automatically publish it on my facebook page as well.
I am using Koala gem but I have some problem with access tokens.
Visiting https://developers.facebook.com/tools/explorer/ I get a short-live token. This token expires after only 2 hours.
My goal is to obtain a long-live token.
I have created a new Facebook app and I have obtained a client-id and client-secret.
Visiting this https://graph.facebook.com/oauth/access_token?client_id=MY-CLIENT-ID&client_secret=MY-CLIENT-SECRET&grant_type=fb_exchange_token&fb_exchange_token=MY-SHORT-LIVE-TOKEN I get long-live token.
Now I can use my long-live token to publish on my page:
user = Koala::Facebook::API.new long_live_access_token
page_access_token = user.get_connections('me', 'accounts').first['access_token']
page = Koala::Facebook::API.new page_access_token
page.put_connections("me", "feed", message: "I am posting on my page!ok!")
It seems to work, but my problems is that the long-live token expires in 60 days so I need to refresh it.
I have found no solution to refresh a long-live token. It is clear that I can't repeat the entire procedure every 60 days!
Ideas?

From the official docs:
These tokens (long-lived) will be refreshed once per day when the
person using your app makes a request to Facebook's servers. If no
requests are made, the token will expire after about 60 days and the
person will have to go through the login flow again to get a new
token.
The token that never expires is a page token.

Related

Using nodemailer & Google OAuth to send email, working for 7 days, but get invalid grant

I've been working to setup Oauth communication for an auto-emailing node.js web app using nodemailer. (I don't wish to use gmail's Less Secure Apps setting).
I've taken steps to get the client id, secret, and refresh token from the oauth playground, and have set up the web app to use a stored refresh token to request new access tokens when it first loads.
It is able to send emails (for about 7 days), then I get error invalid status code 400 on client side, and/or invalid grant on server side.
Going back to google playground and getting another refresh token, then updating it in environment variables, solves this for another week. But I'd like to solve this indefinitely.
I read somewhere "A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of 'Testing' is issued a refresh token expiring in 7 days"... so last week I switched the app to "In Production" (at console.cloud.google.com) and tried having it verified with google. This week, the same issue has recurred suggesting that wasn't the right fix, or that it wasn't yet verified with google.
I don't know if this was done correctly, nor do I know if this is the true solution to this expiring/revoked refresh token, or invalid grant.
I've also come across these explanations:
The user has revoked your app's access.
The refresh token has not been used for six months.
The user changed passwords and the refresh token contains Gmail scopes.
The user account has exceeded a maximum number of granted (live) refresh tokens.
The client has reached a limit of 50 refresh tokens per account if it's not a service account.
(I didn't make ANY changes during the week, so...not sure why these would have changed)
Is the issue the refresh token?
Or the status of the application?
Would it be dns/cname/cloudflare server issues?
For those who have the same issue in the future:
It turned out that google verification wasn't necessary.
It seems like the refresh token expiring after a week or 7 days was due to the placement of the oauth2Client.setCredentials() function call and accessToken variable.
Calling setCredentials() and obtaining the access token INSIDE the SendEmail() function (at runtime, just before sending email, rather than at application start/spinup time) seemed like it enabled the code to more dynamically generate the tokens it needed. After 12 days, it still seems like its working so I'd call this a success.
My guess at why it wasn't working before was because setting credentials outside of a function meant that code only ran once on server/application startup. It would then store the obtained access token in a const.
The access token would eventually expire, and even if called again/later inside of a function to obtain a new access token, it would be unable to change the value of a const property/variable, and so the call would inevitably fail after a week when it failed to renew.
Hope this helps anyone else having a similar issue.
My apologies for the run-on sentences.
There are a lot of causes for invalid grant it sounds to me like your refresh token is expiring.
If your project on google developer console is still in testing, has not been moved to published and has not gone though the google application verification process then refresh tokens have a max two week life span after which they will expire which may explain your invalid grant. The thing is there is no official word from google that this is happening its just what a lot of developers are seeing these days.
Another one is with gmail scopes if the user changes their password this will also cause the refresh token to expire.

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

How is access token refreshed / acquired when using API Client Library for .NET?

I have one quick question related to "acquiring a new access token upon expiration". I have read some tutorials where people write code to manually request a new access token.
In my case I wrote an ASP.NET MVC app to access Google APIs, such as Gmail API, and I am using API Client Library for .NET for that.
After OAuth 2.0 authorization I get back the result object of type AuthorizationCodeWebApp.AuthResult.
Where result.Credential.Token contains AccessToken and RefreshToken properties.
I save the refresh token in my web.config the very first time when it comes back (after the consent screen). All next requests dont have a refresh token, only an access token that expires after 1 hour.
So, my question is - before I make a call to instantiate a Gmail Service, I assign previously saved refresh token:
result.Credential.Token.RefreshToken = WebConfigurationManager.AppSettings["RefreshToken"];
var service = new GmailService(
new BaseClientService.Initializer { HttpClientInitializer = credential });
When result.Credential.Token.AccessToken expires, does Gmail API (or any other API Client Library for .NET) acquires a new access token automatically if result.Credential.Token.RefreshToken was assigned a valid refresh token value previously saved, like in my code sample?
Thank you!
UPDATE - More clarification to my question With the same refresh token, how many times I can aquire a new access token when making calls to Google API?
I will explain: access token expires in 1 hour, right.
If I keep making calls with, lets say, 10 minutes intervals to Gmail API (for example), after 6 calls (1 hour limit), Gmail API will use my refresh token to acquire a new access token. After 6 more calls (1 more hour) the whole thing repeats itself. Question - is there a limit to it? Remember, I am not changing my refresh token. Same refresh token is being used to acquire a new access token. And for how long this repetitive calls may continue without any error?
UPDATE AFTER THE TEST
I let my application run on my local machine in Visual Studio DEBUG mode trying to catch any exception, NO Human interaction.
The application kept receiving AJAX calls to Gmail Action with 2 minutes interval, everything was working fine, I went to the gym, came back 2 hours later - oops, Visual Studio debug is open on this Token has been revoked exception, here we go, so it's clear the token was revoked by the Google API service, as you can see from the Debug window. The only question remains - why, since there are no specific details are provided, there is no Inner Exception just that general error message and no reason, but the source is clear - Google API, we can even see it came back from
Google.Apis.Requests.ClientServiceRequest`1.Execute() в
C:\Users\mdril\Documents\GitHub\google-api-dotnet-client\Src\GoogleApis\Apis\Requests\ClientServiceRequest.cs:row
96
I am guessing the service shuts down (revokes a token) after N number of calls, maybe within certain interval. If some one knows the limitations of Google API in terms of number of calls or time intervals between calls, please let me know.
It seems that Matthew Riley, the custodian of Google API on github, coded some logic to revoke a token based on some criteria: https://github.com/google/google-api-dotnet-client
Long response to comment :
One question though: can this be done indefinitely long, unlimited number of times, or I will get an error at some point?
Refresh tokens can be come invalid for the following reasons:
user can revoke it in there google account.
if a refresh token isn't used for 6 months to get a new access token it will expire automatically.
If a user authenticates your application you get a refresh token if they do it again you get a different refresh token. Both will work. you can do this up to 26 times. on the 27 th time the user Authenticates your application the first one you got will expire. You can only have 26 live refresh tokens. (DONT ASK how I know this! "#¤%&)
So assuming you don't reauthentcate your application to many times, use the refresh token at least once every six months. You can use it as many times as you want.
Update for comment:
I think you are still confused. Access tokens expire after 1 hour. Refresh tokens only expire for the above reasons you can use them as many times as you like. To get a new access token.
However you can only have 25 working refresh tokens.
Lets say I have a windows service application that backs up files to a users Google drive account. A user installs it on a server and authenticates it and gets a refresh token. Every night the windows service runs and backs up the files to google drive, it uses the refresh token to get a new access token.
Lets say this user really likes my auto super imba backup service. He installs it on another server. He gets another refresh token and the application goes about its business uploading files at night
Lets say my super user really has a server farm he installs my application on 25 servers. Those applications will be able to get new access tokens forever.
However if this crazy user installs it the 26 th time on a different server getting a new refresh token for this server. The first server they installed it on will stop working because google only allows you to have 25 outstanding refresh tokens for an application.
This is user application based so you can have any number of users each with a max of 25 refresh tokens

Google API Client: Token has been revoked issue

I'm using the google-api-client gem in my Rails project. I have omniauth and devise working, and I have users authenticate through Google.
I thought I had this going very well, until recently. I've noticed my app will throw an error when it fetches the Google Calendar API after one hour. The expiration is one hour from authentication time, and from then I get this error:
Signet::AuthorizationError (Authorization failed. Server message:
{
"error" : "invalid_grant",
"error_description" : "Token has been revoked."
}):
This is separate from invalid refresh tokens, as I do have the refresh token stored in the database. It is sending the refresh token request, which spurs that error above, with this code:
client = Google::APIClient.new(
:application_name => APP_NAME,
:application_version => APP_VERSION,
)
client.authorization.client_id = CLIENT_ID
client.authorization.client_secret = CLIENT_SECRET
client.authorization.refresh_token = user.auth_refresh_token
token_result = client.authorization.fetch_access_token!
I have been very careful as to not sign in and out of my Google accounts, so I cannot figure out why Google would send back this message. If I refresh the page after 55 minutes, all is okay. If I refresh the page after 1 hour, it complains about the access token being revoked.
Has anyone had this issue before? If so, what did you do to fix it? Was it something you had to change in Google's Developer Console?
I ended up figuring out the issue, so I thought I'd share what fixed it.
In config/initializers/devise.rb, I have:
scope: 'userinfo.profile, userinfo.email, calendar, https://www.googleapis.com/auth/gmail.readonly', prompt: 'select_account consent' }
What did it for me was the prompt: 'select_account consent' part. Asking the user for consent at each login seems to keep the refresh token up to date. When the user logs in via Google I check if there was a refresh token in the response, and if there was I save that to the database. If not, I keep their current refresh token in the database.
In all honesty, I really don't get why it was necessary for me to do this but for other users who've shared their code examples it was fine. Perhaps there was a change in the Google's OAuth2 or maybe there's a discrepancy in my method of handling the authorization.

Refresh LinkedIn token with omniauth before expiration

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

Resources