I'm using GoogleAuthUtil in Google Play Services on Android. After calling GoogleAuthUtil.getToken(context, userName, scope), I got a token like this:
ya29.wQBWztab5kcgMLcMbAI0LwFzHC_DPrxauSWbX4P6KOnBEOgjcm9V7OI9AFr6JGxDY54gP00RemzzgML56_gWRHn8Q5jK16BLY-0y83Gc5vfe3xN-QpyM4d7z
This is an access_token, which can be used in calling Google Apis. Then, how can I get a refresh token to refresh this access_token, because I also use Google oauth java library and YouTube Java Library in my Android project, I want to use these two libraries to maintain/manage the access_token, refresh token and expires_in values. (When using Google oauth java library, the TokenResponse it returned contains access_token, refresh token and expires_in)
Thanks in advance.
You cannot directly get a refreshToken using GoogleAuthUtil.getToken() but if you call getToken() each time you get a 401 error, GoogleAuthUtil will return you a new valid token if needed.
In order to get a refresh token, make sure that your scope is in the following format:
Account account = new Account(mEmail, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE);
mScope="oauth2:server:client_id:"+ OAUTH_WEBCOMPONENT_ID+":api_scope:"+"https://www.googleapis.com/auth/userinfo.email";
return GoogleAuthUtil.getToken(mActivity, account, mScope);
This will give you an authorization code, which can be sent to your web component.
Your webcomponent than can use this authorization code only once to get an access token and refresh token with this code. You have to save the refresh token in your database, so that when the access code is no longer valid you can get a new access token when needed.
POST /oauth2/v3/token HTTP/1.1
Host: www.googleapis.com
Content-length: 233
content-type: application/x-www-form-urlencoded
user-agent: google-oauth-playground
code=4%2FVL2YMuPMheOP2-0vyKBSfGd-4er5GsMY17Ecp8ITK4U&redirect_uri=https%3A%2F%2Fdevelopers.google.com%2Foauthplayground&client_id=407408718192.apps.googleusercontent.com&client_secret=************&scope=&grant_type=authorization_code
You can simulate how this works here:
https://developers.google.com/oauthplayground/
Call requestServerAuthCode(String, true) instead requestServerAuthCode(String) which forces the request to include a refresh_token when it succeeds.
https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInOptions.Builder.html#requestServerAuthCode(java.lang.String, boolean)
val task = GoogleSignIn.getSignedInAccountFromIntent(data);
task.addOnSuccessListener {
val account = task.getResult(ApiException::class.java)
val authCode = account!!.serverAuthCode
// Send authcode to server to exchange access and refresh tokens.
exchangeAuthCodeForAccessToken(authCode)
}
Related
I want to get subscription data using Purchase.Subscription API. For this I am trying to get token from https://accounts.google.com/o/oauth2/token.
As per documentation ,It should return me following response:
{
"access_token" : "ya29.ZStBkRnGyZ2mUYOLgls7QVBxOg82XhBCFo8UIT5gM",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/zaaHNytlC3SEBX7F2cfrHcqJEa3KoAHYeXES6nmho"
}
But when I tried to follow same steps, I am not getting refresh_token field in response.
I am sending below fields as mentioned in documentation:
grant_type=authorization_code
code=<the code from the previous step>
client_id=<the client ID token created in the APIs Console>
client_secret=<the client secret corresponding to the client ID>
redirect_uri=<the URI registered with the client ID>
Please help me what I am missing.
ok. I found actual issue. This is really lack of documentation on API side. We will have refresh_token value only for first time calling api https://accounts.google.com/o/oauth2/token with below data after allowing app to manage your developer account:
grant_type=authorization_code
code=<the code from the previous step>
client_id=<the client ID token created in the APIs Console>
client_secret=<the client secret corresponding to the client ID>
redirect_uri=<the URI registered with the client ID>
If you are not getting refresh_token, you can follow steps as mentioned in this answer and try to fetch refresh_token again
I have an api that returns this after authentication
"token_type":"Bearer",
"expires_in":86400,
"access_token":"XXXXXXXXX",
"refresh_token":"XXXXXXXXXXX"
I have successfully saved the access token, refresh token and the expires_in in an sqlite database. How can i check if the token has expired ?
Here is what i have so far
if(new Date().after(expiresAt)){
Toast.makeText(this, "Token expired", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "You still have time", Toast.LENGTH_SHORT).show();
}
expiresAt contains the expires_in value
I would suggest you to use framework such as retrofit to help you manage the authentication without checking the expiresAt in every single API call.
In Retrofit, or its HTTP client OkHttp. You can handle authentication by using new Authenticator API, designed specifically for your refresh token/auto login scenario. You may refer to this link for implementation details refreshing-oauth-token-using-retrofit-without-modifying-all-calls
Whenever your API call return a "401 unauthorized" respond, it will trigger authenticator method, run your refresh token API and resend the original request with new access token.
I am using retrofit 2.0 to build a Bitbucket REST client on Android.
As far as I'm concerned, OAUTH2.0 provides "Implicit grant" which gives the client the access Bearer token immediately when the user logins to their account when are prompted to.
Bearer tokens are tokens that can be used to access protected resource. Anyone who has a bearer token has the permission to access the protected resource as anyone else who also has the bearer token. (according to this doc from IETF)
Please correct me if I'm wrong, but I thought using implicit grant, after user logins in their Bitbucket account, I will have the Bearer access token. After that, I can use this access token to access protected resource on Bitbucket (like create a new repository).
So I have built my android using the OAUTH2.0 Implicit grant as described in the Bitbucket doc. Note that they described the response will have #access_token={token}&token_type=bearer
And this is what I actually received from Bitbucket after logging in:
your://redirecturi#access_token=lEuvneW39onVrnNR-jvZfirI43fwi5Wdc0YaaMROBk5YKJsd2ulXm20vJDdOBjf8I-Ne2r2vC8-_FHECSLw%3D&scopes=pipeline%3Awrite+webhook+snippet%3Awrite+wiki+issue%3Awrite+pullrequest%3Awrite+repository%3Adelete+repository%3Aadmin+project%3Awrite+team%3Awrite+account&expires_in=3600&token_type=bearer
My first question:
What exactly is the Bearer access token from the above response?
Is the part lEuvneW39onVrnNR-jvZfirI43fwi5Wdc0YaaMROBk5YKJsd2ulXm20vJDdOBjf8I-Ne2r2vC8-_FHECSLw the Bearer access token? Do I have to include the "%3D" (which is the char "=" encoded in ASCII)? Doesn't the Bitbucket doc mean that everything exceptfor the last "&token_type=bear" is the Bear access token?
That's not all. Bitbucket doc's instruction to make request as follow:
Send it in a request header: Authorization: Bearer {access_token}
So I set this request up to create a new repository in accordance with the API of Bitbucket:
#POST("repositories/{username}/{repo_slug}")
Call<Repository> createRepository(
#Header("Authorization") String auth,
#Path("username") String userName,
#Path("repo_slug") String repoSlug);
But everytime, I got the respones with status code 401 and message error:
Access token expired. Use your refresh token to obtain a new access token.
When I tried to POST the same request using DHC by Restlet (a chrome extention like Postman), a pop up appears and requires me to login to Bitbucket. If I refuse to do so, I got the same error 401 response. If I do login, then it works.
My second question: Why do I have to provide my credentials again?
I think there's something wrong here. I thought with the Bearer access token, I should be able to access the protected resource without having to log in before the access token's expire time has been reached. Why do I have to enter my credentials the second time? This is not what is described in the "Implicit grant" approach here by IETF.
The value of the access_token starts after access_token= and ends before the next parameter scopes so before &scopes=. The formatting of the fragment part is specified in https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2 which in its turn points to https://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.13.4.1 which says:
[...] The name is separated from the value by = and name/value pairs
are separated from each other by & [...]
So your access token value by spec is lEuvneW39onVrnNR-jvZfirI43fwi5Wdc0YaaMROBk5YKJsd2ulXm20vJDdOBjf8I-Ne2r2vC8-_FHECSLw%3D but I agree that the ending %3D is suspicious and may be an error on the sender's part.
If your access token is expired (which is what it seems to be) you need to get a new one using the Implicit grant again, or using the Refresh token grant.
The online reference for Google APIs for Android, shows a public method summary for the Games class which includes:
static PendingResult<Games.GetTokenResult> getGamesAuthToken(GoogleApiClient apiClient)
But the latest release available (8.4.0) does not include this method. I use this to get the APIs:
dependencies {
compile 'com.google.android.gms:play-services:8.4.0'
}
Where is Games.getGamesAuthToken?
This is actually a documentation problem. getGamesAuthToken() has been removed because it was not as secure as it needs to be.
For reference, you can read http://android-developers.blogspot.com/2016/01/play-games-permissions-are-changing-in.html
The best way to handle this is to:
After the player is authenticated on the device:
Get an auth code to send to your backend server:
GetServerAuthCodeResult result =
Games.getGamesServerAuthCode(gac, clientId).await();
if (result.isSuccess()) {
String authCode = result.getCode();
// Send code to server.
}
On the server, exchange the auth code received for a token by
making an RPC to https://www.googleapis.com/oauth2/v4/token to exchange the auth code for an access token.
You’ll have to provide the server client ID, server client secret (listed in the Developer Console when you created the server client ID), and the auth code.
See more details here: https://developers.google.com/identity/protocols/OAuth2WebServer?utm_campaign=play games_discussion_permissions_012316&utm_source=anddev&utm_medium=blog#handlingresponse.
Once you have the access token, the player's ID is retrieved using:
www.googleapis.com/games/v1/applications/<app_id>/verify/ using the access token.
Pass the auth token in a header as follows:
“Authorization: OAuth ”
The response value will contain the player ID for the user. This is the correct player ID to use for this user.
This access token can be used to make additional server-to-server calls as needed.
I use the following to get an authentication that I can access a backend from an Android app. This is described here https://developers.google.com/identity/protocols/CrossClientAuth.
StringBuffer sb = new StringBuffer();
sb.append("oauth2:server:client_id:");
sb.append(getString(R.string.google_app_id));
sb.append(":api_scope:");
sb.append("profile email");
final String scope = sb.toString();
String token = GoogleAuthUtil.getToken(Activity.this, mAuthAccount, scope);
(mAuthAccount was previously set using AccountPicker.newChooseAccountIntent.)
The above returns a short lived authentication code which sometimes has expired. I would like to check it against google servers, but calling https://www.googleapis.com/oauth2/v1/tokeninfo?access_token= with the returned token string from GoogleAuthUtil.getToken returns "invalid token".
How do I verify that the authentication code has not expired before I try to use it?
Edit: The returned string is not a token, but an authorization code which can be exchanged with a google API to obtain a token (The returned string begins with "/4" and not "/1" or "/2" if I remember correctly).
The code does not always work (on my server) and I would love to be able to check if the code can be used or has expired.
You don't need to use app_id to get oauth token, You just need to change scope
"oauth2:" + Scopes.PLUS_LOGIN
In this case your scope can be plus.login
More Info:
Authorizing with Google for REST APIs
Scopes Class
As you can see in Validating the token documentation:
https://www.googleapis.com/oauth2/v1/tokeninfo
Accepts an access token and returns information about that access
token including which application was it issued to, the intended
target of the token, the scopes the user consented to, the remaining
lifetime of the token, and the user ID.
Below is an example of such a request:
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=1/fFBGRNJru1FQd44AzqT3Zg
The TokenInfo endpoint will respond with a JSON array that describes the token or an error.
On the wire, the response looks similar to the following:
{
"audience":"8819981768.apps.googleusercontent.com",
"user_id":"123456789",
"scope":"profile email",
"expires_in":436
}
If the token has expired, has been tampered with, or the permissions
revoked, the Google Authorization Server will respond with an error.
The error surfaces as a 400 status code, and a JSON body as follows:
{"error":"invalid_token"}
By design, no additional information is given as to the reason for the failure.
So invalid_token is a valid answer when the token has expired, has been tampered with, or the permissions revoked
EDIT
It is possible to receive invalid_token just after the token is returned from GoogleAuthUtil.getToken because GoogleAuthUtil may have cached it. In this case you should call GoogleAuthUtil.invalidateToken.
You can find more information here:
http://developer.android.com/reference/com/google/android/gms/auth/GoogleAuthUtil.html
EDIT 2
I just notice that you are missing https://www.googleapis.com/auth/plus.login in your scope. Please try to add it because seems mandatory to use Cross-client Identity.
The token retrieved by GoogleAuthUtil.getToken() in this fashion is not an access token, it's an ID token.
Try the following URL instead (note id_token query parameter instead of access_token):
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=<ID TOKEN>