Oauth token invalid when trying to use the same token twice? - ruby-on-rails

Hey guys I'm trying to use the Dailybooth api, and getting access to the user via oauth.. I'm very new to ruby/rails and this is my first time working with an api and oauth. Here's the thing though
if params[:code] #if the code is avaible, use the code
dailybooth.oauth_token(params[:code])
#oauth_token_ = params[:code]
session[:oauth_code] = #oauth_token_
else #else, attempt to use the session
if session[:oauth_code] #If session is set
dailybooth.oauth_token(session[:oauth_code]) #sign in using session
else #else, check if the params[:code] is set and use, or redirect to dailybooth authorize
if params[:code]
dailybooth.oauth_token(params[:code])
#oauth_token_ = params[:code]
session[:oauth_code] = #oauth_token_
else
#first redirect the user to the authorize_url
redirect_to dailybooth.authorize_url
end
end
end
#make request to the api
#info = dailybooth.get('/users.json')
if #info['error']
if #info['error']['error_code']
if #info['error']['error_code'] == 302 #if getting invalid token, request another token.
session[:oauth_code] = nil
#info = "Getting Errorcode 302, invalid token."
#first redirect the user to the authorize_url
# redirect_to dailybooth.authorize_url
end
end
end
So, that is the index of my app. When I first go to the index, I'm immediatly transfered to dailybooth to authorize my account, after authorizing, dailybooth returns me to my site with a URL that looks like so "http://site.com/?code=XXX" and "dailybooth.get('/users.json')" goes to work, it actually get's the user information. but if I try to refresh, going to the same "http://site.com/?code=XXX" it'll say the token is invalid. Why is that? The token was just valid and now it's not working. Any suggestions? Is this an error on my end?

My guess is that the problem is you are storing the dailybooth OAuth Token and not the Access Token. Your call to dailybooth.oauth_token(...) probably generates an Access Token and you should be storing that, not the params[:code] (OAuth Token). Generally OAuth works like this:
The user is redirected to the site to give your application access.
The user is redirected back to your site with an OAuth Token giving you access to certain parts of the API or external site.
You trade that OAuth Token in for an Access Token that you can use to access the API and site at any time.
Once that OAuth Token has been traded in for an Access Token it can't be used again, but the Access Token can.
Check the dailybooth object, you can probably access the actual Access Token somehow (dailybooth.token or dailybooth.access_token maybe?). Make sure you store that access token in your session or database, and then use that access token upon returning to the page instead of the params[:code] that has at this point become invalid.

I cannot help you with Ruby as I've never used it, however I can add to the comment above what is the flow in OAuth 2 (which is definitely the preferred choice to use):
The user goes to your site and needs to access functionality that requires the use of a Third-Party OAuth provider API (your site is Consumer and the Third Party is Provider and I will call them this way below)
Consumer makes internal HTTPS request to the Provider to get unauthorized Request token.
Consumer redirects the user to the Provider with the Request token it just received in step 2, in order for this token to be authorized by the user at the Provider's site.
After the user authorize the Request token (eventually), the Provider creates Verifier String and redirects the user back to the callback with this Verifier String and the Request Token, which is very important. In fact, this is the part of OAuth 2, which identifies that the user has really authorized the token (the Verifier String).
Consumer makes internal HTTPS request with the Request token and this Verifier String to get Authorized Access Token.
At this point if the Consumer receive Access Token, he has the right to access the protected API with the user's permission.
Here is a good explanation of why the Verifier is important and added to the protocol:
http://hueniverse.com/2009/04/explaining-the-oauth-session-fixation-attack/

Related

Session empty after redirect

I've a React JS app, which makes this request to my back-end API. i.e
window.location = "https://my-server.com" + "/gmail/add_account";
cannot set HTTP headers for window.location see this
this server endpoint redirects to Google OAuth page, which returns a response to my redirect_uri.
def add_account
# no auth headers sent here, because front-end has used window.location
gmail_service = GmailService.new
session[:uid] = params["uid"]
redirect_to gmail_service.generate_authorization_url()
end
def oauth_postback
# session object is {} here
# Since there are no authorization headers, I cannot identify my app's user
# How can I identify my app's user here?
end
The problem I'm facing is that when the OAuth flow sends the response to my redirect_uri it does not return include any authorization header, due to which I'm unable to identify which user of my app has launched this OAuth flow.
I've tried setting up a session variable in the /gmail/add_account endpoint, which works fine. After this endpoint redirects to the OAuth screen, and the Oauth flow sends a response to my Oauth redirect_uri, there my session object is {}.
How can I implement this flow such that I know which user has launched this OAuth flow?
You have basically two options:
the state parameter
The state parameter is part of the OAuth2 spec (and is supported by Google). It's a random string of characters that you add to the authorization URL (as a query parameter), and will be included when the user is redirected back to your site (as a query parameter). It's used for CSRF protection, and can also be used to identify a user. Be sure that if you use it, it's a one-time value (e.g. a random value that you store in your db, not the user's ID).
sessions with cookies
If the user has previously logged in, you should be able to identify them by their session cookie. It sounds like this is the approach you're currently taking, but the session is getting reset.
It's difficult to debug this without knowing more about your stack/code, but a good first step would be just trying to load your callback URL without the redirection to Google to see the session object is still empty. If so, that would indicate an issue with how you've implemented sessions generally and not something specific to this flow.
As a note, based on the code you've shared, I'm not sure how params["uid"] is getting set if you're doing a redirect without any query parameters or path parameters.
Finally, you may consider using a managed OAuth service for something like this, like Xkit, where I work. If you have a logged in user, you can use Xkit to connect to the user's Gmail account with one line of code, and retrieve their (always refreshed) access tokens anywhere else in your stack (backend, frontend, cloud functions) with one API call.

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

When exchanging the Amazon Alexa grant_code for an access_token, where are the credentials?

I am trying to write the exchange and access endpoints and the docs here (https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/linking-an-alexa-user-with-a-user-in-your-system#h2_login) are not clear on a couple of things:
how is the call to exchange a grant code for access token made - is it GET with credentials in QS or is it a POST with credentials in a body?
Is the access token delivered only in the JSON for an intent call or is it set properly as a bearer token?
It's a POST with credentials in the request body. Amazon follows the Oauth2 RFC correctly in this case.
The access token is delivered by Amazon only in the JSON for the intent request and not properly set as a bearer. This is annoying.
In my case, I had to hack around it by first validating if the request was a valid alexa request which contained a session with an access token, then setting the HTTP_AUTHORIZATION header to Bearer <token>, then using existing request auth logic to authenticate (I was using Django with django-oauth-toolkit, so YMMV if you're using something else).
That code looks something like this:
# get the access_token from the POST request
if access_token is not None:
request.META["HTTP_AUTHORIZATION"] = "Bearer " + access_token
if not hasattr(request, 'user') or request.user.is_anonymous():
user = authenticate(request=request)
if user:
request.user = request._cached_user = user
if request.user.is_authenticated():
# Do whatever with the logged in user

Refresh token with Twitter API

I'm wondering if Twitter has an API endpoint that exchanges an expired access token with an active one. The way that I have the login flow working right now goes something like this.
// Request a token and redirect to the authorization page
$token = $this->twitter->getRequestToken();
// Set the session data
$this->session->set_userdata('oauth_token', $token['oauth_token']);
$this->session->set_userdata('oauth_token_secret', $token['oauth_token_secret']);
// Redirect the user to the authorization page
header('Location: https://api.twitter.com/oauth/authorize?oauth_token='.$token['oauth_token']);
The page that the user is redirected to will prompt the user to authorize my app each and every time they want a valid access token. Upon accepting the authorization, the user will be redirected to the callback URL. At my callback URL, the following happens
// Get the parameters from the URL
$token = $this->input->get('oauth_token');
$verifier = $this->input->get('oauth_verifier');
$oauthToken = $this->session->oauth_token;
$oauthSecret = $this->session->oauth_token_secret;
// Get the access token
$access = $this->twitter->getAccessToken($verifier, $oauthToken, $oauthSecret);
Does such a way exist for an access token to be generated without having to authorize my app each and every time?
According to Twitter's OAuth FAQ, tokens don't expire unless a user explicitly rejects your application or an admin suspends your application.
If you want your users to be able to login repeatedly without having to reauthorize, you'll need to come up with a mechanism for storing the tokens (cookies, database, etc.).

How to grant acces permantly with OAuth2

I have tried to use OAuth2 to build a group settings service with the following:
def groupSettingsService(request):
CLIENT_SECRETS = os.path.join(os.path.dirname(__file__), 'client_secrets.json')
FLOW = client.flow_from_clientsecrets(CLIENT_SECRETS, scope=['https://www.googleapis.com/auth/apps.groups.settings'], message=tools.message_if_missing(CLIENT_SECRETS))
storage = Storage('groups-settings.dat')
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = run(FLOW, storage)
http = httplib2.Http()
http = credentials.authorize(http)
return discovery.build('groupssettings', 'v1', http=http)
But the problem is when the token isn't valid anymore (expires) it redirect to a page to tell a user to grant access again to that scope...things that is inappropriate for API calls !
is there a way to work with a username/password or client_secret to grant a full access permanently to the API without asking to grant access or not ?
You need to ask for access_type=offline when you redirect the user to Google.
You will than get an code, which can be exchanged (by POSTing with your client_id and client_secret) into an access_token (that is the one you are already using) and a refresh_token.
When your access_token expires, you can POST the refresh_token, client_id and client_secret to get another access_token. You can do that multiple times if you need (or weeks later...)
Did you save the credentials to storage upon getting a credentials successfully?

Resources