I am having trouble getting a one-time authorization code from Google. I am attempting to get the authorization code from an Android client so that I can send it to my Rails backend (web client).
In my Google Cloud Developer Console I have an application with two Client IDs:
Client ID for web application (for my rails backend)
Client ID for Android application (for my android client). The SHA1 used is from ~/.android/debug.keystore
Suppose the Web Application Client ID is 12345.apps.googleusercontent.com
Suppose the Android Client ID is 67890.apps.googleusercontent.com
This is some of my code:
private final static String WEB_CLIENT_ID = "12345.apps.googleusercontent.com";
private final static String GOOGLE_CALENDAR_API_SCOPE = "audience:server:client_id:" + WEB_CLIENT_ID;
private void getAndUseAuthToken(final String email) {
AsyncTask task = new AsyncTask<String, Void, String>() {
#Override
protected String doInBackground(String... emails) {
try {
return GoogleAuthUtil.getToken(AddExternalCalendarsActivity.this, emails[0], GOOGLE_CALENDAR_API_SCOPE);
} catch (UserRecoverableAuthException e) {
startActivityForResult(e.getIntent(), IntentConstants.REQUEST_GOOGLE_AUTHORIZATION);
} catch (IOException e) {
e.printStackTrace();
} catch (GoogleAuthException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String authToken) {
if (authToken != null) {
saveTokenAndGetCalendars(email, authToken);
}
}
};
String[] emails = new String[1];
emails[0] = email;
task.execute(emails);
}
Some additional notes
I am hitting the GoogleAuthException and receiving "Unknown" as the message detail
I'm unable to add additional members in the permissions of the Google Cloud Console for this project - when adding a new member a popup appears with "Server Error. Whoops! Our bad.". I have sent feedback to Google twice.
I'm referring to this documentation. Notice the quote below. By "fixed", are they saying that I do not need to prepend audience:server:client_id in my GOOGLE_CALENDAR_API_SCOPE variable? I've tried both with and without and still getting the same GoogleAuthException.
In this situation, the Android app can call the
GoogleAuthUtil.getToken() method on behalf of any of the Google
accounts on the device, and with a scope argument value of
audience:server:client_id:9414861317621.apps.googleusercontent.com.
The prefix audience:server:client_id: is fixed, and the rest of the
scope string is the client ID of the web component.
If I use this scope, I can authenticate with google from device. However, the documentation I've read suggests that I need to use the server web client id (which is in the same google console project as the android client id) in the scope in order for the server to hit the google api on behalf of the user who authorized it on the android client:
private final static String GOOGLE_CALENDAR_API_SCOPE = "oauth2:https://www.googleapis.com/auth/calendar";
UPDATE 1
I originally added in answer:
The reason for my first problem - I am hitting the GoogleAuthException and receiving "Unknown" as the message detail - was a mistake I made when adding the android client id in the cloud console. The SHA1 was correct but I did not type the package name correctly. I used com.company.app when my android package is actually com.company.android.app.
The code in the original question works fine. Just make sure you have all the necessary clients in your Google Cloud Console project.
But another problem still exists. When I send the one-time authorization token returned from GoogleAuthUtil.getToken() to the Rails backend, and then try to exchange it for an access_token and refresh_token, I get the follow:
Signet::AuthorizationError:
Authorization failed. Server message:
{
"error" : "invalid_grant"
}
This google documentation and several SO posts suggests that I need to set access_type=offline. But I think that is when you are requesting the one-time authorization code and offline access from a Web Server. I'm trying to request the one-time authorization code from an Android client and send it to the web server.
Is this possible with GoogleAuthUtil.getToken()?
UPDATE 2
Google Plus login must be in the scope even if you're only trying to access the calendar:
private final static String GOOGLE_CALENDAR_API_SCOPE = "oauth2:server:client_id:" + WEB_CLIENT_ID + ":api_scope:https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/calendar";
This SO post was helpful. Also, Google's Cross Client identity documentation does state:
[Note: This policy in being rolled out gradually. For the moment, when access tokens are involved, it only applies when the requested scopes include https://www.googleapis.com/auth/plus.login.]
I'll summarize in an answer if the token exchange works on Rails backend.
Two things solved this for me:
Make sure the Android and Web Client IDs are setup in correctly in the same Google Cloud Console project.
Use the correct scope. Plus login is required even if you're only accessing the calendar api:
// the id of the web server that is exchanging the auth code for access and refresh tokens
private final static String WEB_CLIENT_ID = "12345.apps.googleusercontent.com";
private final static String GOOGLE_CALENDAR_API_SCOPE = "oauth2:server:client_id:" + WEB_CLIENT_ID + ":api_scope:https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/calendar";
Here's what I've done:
final String authToken = GoogleAuthUtil.getTokenWithNotification (this.context, account.name, "oauth2:" + AuthUtils.profileScope, new Bundle (),
Contract.authority, new Bundle ());
But you plain getToken should work the same for a foreground activity.
Take this token, send it to the server in an a way you can use it like an HTTP header (over HTTPS). As long as the server scope is a subset of the scope used to acquire the token, you shouldn't have a problem. The server uses the server id and the android client uses the android client id.
You should set your scope to either:
oauth2:https://www.googleapis.com/auth/calendar
or
oauth2:https://www.googleapis.com/auth/calendar.readonly
See https://developers.google.com/google-apps/calendar/auth
Edit: OK I see what you are trying to do. The scope value is a space separated list so you would likely need to append audience:server:client_id: to your scope like:
"oauth2:https://www.googleapis.com/auth/calendar audience:server:client_id:12345-your-web-component-client-id"
Related
As the title says, I'm trying to use the Google Sign-In API with a Spring Boot backend server, as described here.
Just to describe the context, the Spring backend is basically a resource+authentication server, that is currently providing Oauth2 authentication to a second spring boot application containing the frontend website, via Google SSO or simple form login (similar to what's described here).
My original idea was to mimic the #EnableOauth2Sso annotation by simply providing an access token to the android app and attach it to every request as "Bearer ".
Using the user credentials for this was pretty straightforward: I simply make a request to the server at "/oauth/token", using those credentials inserted by the user as authentication and I correctly receive the access token.
Now, I have absolutely no idea on how to build a similar procedure with the Google API in Android. The tutorial page I linked before describes how to get a token ID and how the server should validate it, but after that I don't know what to do.
So far I've managed to add a filter to the security chain that simply checks the token like this:
private Authentication attemptOpenIDAuthentication(#NonNull String tokenString){
String clientId = authServices.getClientId();
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, factory)
.setAudience(Arrays.asList(clientId, androidClient))
.build();
try {
GoogleIdToken token = verifier.verify(tokenString);
if (token != null) {
return authServices.loadAuthentication(token.getPayload());
} else {
throw new InvalidTokenException("ID token is null");
}
} catch (GeneralSecurityException | IOException e) {
throw new BadCredentialsException("Could not validate ID token");
}
}
This manages indeed to create an Authentication object, but how can I generate an access token after the authentication filtering?
To recap, so far I've got:
The Android app successfully retrieves the Google token ID and sends it to the server
The server sucessfully intercepts the request and validates the token
I'm basically missing the third point where I return a proper access token to the Android client.
Here you are a simple scheme to better understand the situation:
Is there any other way to validate the token and get an access token from the server, or should I completely change the authentication procedure on Android?
As far as I can tell: Yes, you need an access token from the server. If I understand this correctly, a webapp is already authenticated via Oauth on your backend, so the procedure is similar here: Load the user with the google-ID and generate a token. In my application I used a JWT which is valid for 30 days. If the token expires, the Google authentication in the app is usually still valid, so the token can be renewed using the Google ID. With Oauth you can also send a refresh-token directly.
It is important that the app always checks the Google authentication first and only in a second step that of the backend.
For the Authentication process on the backend u may need to manually implement a dedicated securityConfiguration for this. Have a look at the jhipster project, they implemented a custom jwt-authentication which may give you an idea how it works.
I'm developing an Android app which is using Oauth2 tokens to get authorization in order to access secured resources. I'm using a third party platform as the authentication server (using OpenId Connect). Basically my problem is that I want to deal with an expired refresh token.
Current scenario
I've got a NetUtils class which acts like a singleton and manages all my requests using a secured rest template. That rest template injects the required Authorization header for each request using a request wrapper. The NetUtils class deals whith tokens and timeouts, saving them in user preferences and refreshing them when it's needed.
However, the problem comes when the refresh token itself expires. As I'm using the Authorization code flow, I need to open a WebView and redirect the user to the login page, but I notice it when the NetUtils class determinates the refresh token has expired. Ideally, the app would launch a WebView, the user would login again and the stored request would be executed. Here it is my code to refresh the access token:
private AccessToken refreshToken(String idClient, String clientSecret, AccessToken accessToken) {
MultiValueMap<String, String> clientAuthenticationForm = new LinkedMultiValueMap<>();
clientAuthenticationForm.add("grant_type", "refresh_token");
clientAuthenticationForm.add("refresh_token", accessToken.getRefreshToken());
clientAuthenticationForm.add("client_id", idClient);
clientAuthenticationForm.add("client_secret", clientSecret);
try {
long lastClientRefresh = mPrefs.getLong(Preferences.LAST_LOGIN_TIME, Long.MIN_VALUE);
boolean refreshTokenExpired = lastClientRefresh
+ TimeUnit.SECONDS.toMillis(accessToken.getRefreshExpiresIn()) < System
.currentTimeMillis();
if (!refreshTokenExpired) {
return regularRestTemplate
.postForEntity(tokenUrl(), clientAuthenticationForm, AccessToken.class)
.getBody();
}else{
//How to cope with this?
return null;
}
} catch (Exception ex) {
Log.e(TAG, ex.getMessage(), ex);
throw ex;
}
}
Other choice
Other choice would be to make the refresh token long lived and refresh it each time the app starts, for example. I have to mention that client_id and client_secret are currently being hardcoded in the app (although client credential grants are not meant to be enabled in production, so there's still the need to provide a username and password to retrieve a token).
What would be the best practice here?
I think I can't suggest you how to code in Java, but I also had some troubles with refresh_token while creating application in PHP so maybe my thoughts will help you with something.
At first I was looking for refresh_token which never expires (like in Google API) so I can even hardcode it and use whenever I want to create a new access_token. Anyway it's really hard to do in oAuth2. So I have found a interesting look on this problem here:
Why do access tokens expire?
It showed me a bit other way to work with refresh_token. I have set on my oAuth service that it generates and returns a new refresh_token everytime I use refresh_token to obtain a new access_token. That part helped me most:
https://bshaffer.github.io/oauth2-server-php-docs/grant-types/refresh-token/
And there we got something like:
$server = new OAuth2\Server($storage, array(
'always_issue_new_refresh_token' => true, // this part
'refresh_token_lifetime' => 2419200,
));
In this case I have a long live refresh_token which I can store somewhere and when I need it I will use it to get a new access_token, but response will also provide me a new refresh_token which I can store again and use it later for obtaining a new access_token.
So in your case I think the best way is to keep generating refresh_token everytime you ask for access_token with refresh_token. And if user will not use your APP for longer time, I think he should authorize himself again.
I'm trying to develop an application that makes use of some basic Google+ Apis. The app request a token from Google that then upload on my server and my server check for its validation with Google. What I don't understand (I'm a bit confused) is what's the difference between Client ID for Web application and Client ID for Android application in Google Developers Console. I've tried both of them on the Android app and both work (successfully obtained a token). Obviously, when using the Web Client ID, my SCOPE that I pass using GoogleAuthUtil.getToken() is different from the one using Android Client ID. So, what's the difference between them? I think that I should go for the Android Client ID, but I'd like to know the really differences.
On client side I use:
final String token = GoogleAuthUtil.getToken(mContext, userEmail, G_PLUS_SCOPE);
Where G_PLUS_SCOPE = oauth2:https://www.googleapis.com/auth/plus.me
On server side, I check with Google with this code:
GoogleIdToken token = GoogleIdToken.parse(mJFactory, getRequest().getAuthToken());
if (mVerifier.verify(token)) {
GoogleIdToken.Payload tempPayload = token.getPayload();
if (!tempPayload.getAudience().equals(mAudience)) {
problem = "Audience mismatch";
errorMessage = new ErrorMessage(ErrorCodes.AUDIENCE_MISMATCH,problem,null);
mResponse = new ErrorResponse( errorMessage);
}
else if (!mClientIDs.contains(tempPayload.getAuthorizedParty())) {
problem = "Client ID mismatch";
errorMessage = new ErrorMessage(ErrorCodes.CLIENT_ID_MISMATCH,problem,null);
mResponse = new ErrorResponse(errorMessage);
}
I also don't understand what's the exact value of mAudience. Do I need to put the Client ID as mAudience? And, is the mClientIDs the array containing all the key (Including the Android client ID key)?
Thanks for your help
EDIT: Following http://android-developers.blogspot.it/2013/01/verifying-back-end-calls-from-android.html I've read that the Audience is the Client ID for Web Application and the mIds are all the ID for installed application (1 for me because I've only Android). But I'm not sure if this is the right way of thinking it for every case.
I don't have answer to your question but I found this blog which can help you out:
http://www.androidhive.info/2014/02/android-login-with-google-plus-account-1/
I hope this helps you.
Technically the audience is the client ID which the ID token is intended to authenticate the user to, where the authorized party is the client ID which the ID token was issued to:
http://openid.net/specs/openid-connect-core-1_0.html#IDToken
You should verify both when both are provided. When requesting an ID Token from your Android app, you should use the Client ID of your server to make the request. Your server should then verify that it is the audience for the token when it receives it from your Android client.
I have a simple application that lets a user draw pictures. There are Android, IOS, and web-based versions. I also let users store their pictures on our App-engine servers and I want them to be able to collaborate with other users. I want to use Google accounts for authentication and the basis of some permission scheme.
I do not know how to validate/authenticate a user’s Google account on android (or IOS). I am hoping somebody can help or point me in the right direction. Here is what I understand so far:
On the Web-based client, I just use Google-web toolkits UserService. However for my app-engine servlets i'm not sure what I should use. Currently the servlets have code like this:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
OAuthService oauth = null;
oauth = OAuthServiceFactory.getOAuthService();
User user = oauth.getCurrentUser();
// Do stuff
}
In my android application I think I'm supposed to do something like:
1) Get the Account from the AccountManager
2) Call:
accountManager.getAuthToken
(account, // Account
"oauth2:https://www.googleapis.com/auth/userinfo.profile",//AUTH Token Type
null, // Options
this, // Activity
new MyAccountsManagerCallBack(), // call-back
null); // Handler
This will give me authorization token.
3) ?? profit ??
This is where I am lost. Do I send this authorization token as a clear-text query parameter to my app-engine server, then make a request from the web server to the userinfo/profile api? That doesn’t seem secure.
Is there some way to make the pervious code with OAuthService work?
The samples for OAuth 2 use the Google task API, however I want to use my app-engine API. I’ve found information for OAuth 1 using cookies, webviews, title, etc, but nothing on OAuth 2, and none of them really tell me how to validate server side.
I really have no clue what I should be doing here. I would appreciate any assistance.
To clarify, this is an example of my java servlet served on app engine:
public class ServletSecureData extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
UserService usersrvc = null;
usersrvc = UserServiceFactory.getUserService();
User user = usersrvc.getCurrentUser();
if(user == null)
throw new IOException();
Random r = new Random(System.currentTimeMillis());
int num = r.nextInt(10);
PrintWriter out = response.getWriter();
out.printf("Security !! %s radioactive man! %d", user.getEmail(), num);
out.close();
}
}
This servlet was protected with a security constraint defined in the web.xml file. I wanted to be able to access this servlet using an android client. I thought that I had to used Oauth but it turned out I needed to use an older deprecated service ClientLogin
I based my implementation off the code from this site: http://blog.notdot.net/2010/05/Authenticating-against-App-Engine-from-an-Android-app
I'm building a webservice and would like to use the user's google account credentials.
The service runs on GAE and will have a web client and an Android native client.
This is my first attempt of something like this and I've been reading about OpenID and the Android AccountManager library.
I'm still not sure what are my options in terms of storing the users in my Datastore. What Identifier should I use ? Is it possible to use OpenID on a native Android application ?
Any help and/or pointers would be appreciated. Thanks.
We had a similar requirements on the last project: GAE backend with GWT frontend and Android/iPhone clients. Also, we did not want to store user credentials.
So we choose to use OpenID, which is unfortunately a Web standard and does not play well with mobile devices, but is doable.
On the GAE side we simply enabled federated login which gave us OpenID.
On mobile devices, when user needs to login we present to them a list op OpenID authenticators (Google, Yahoo, etc..). Then we open a native browser (not embedded browser) and direct user to chosen OpenID authentication site. The upside is that user's browser usually already has username/pass remembered, so this step just requires user to press one button.
This is all pretty straightforward. Now here is the tricky part:
After user confirms login, OpenID redirects back to our GAE return url (you need to provide this url when request is made). On this url we create a custom URL, for example:
yourappname://usrname#XXXYYYZZZ
where XXXYYYZZZZ is auth token. We get this token from the return page where it's stored as an ACSID cookie: we used some JSP to read this cookie and wrap it into above custom URL.
Then we register our Android and iPhone apps to handle the yourappname:// URLs, so that when user cliskc this link, our app is invoked and the link is passed to it. We extract user name and token from this link and we use it in REST requests to the GAE backend.
If you have any more questions I'd gladly update this post.
Update:
The user session cookie on production AppEngine is named ACSID, while on development AppEngine server it's named dev_appserver_login.
I spent about a week to find a suitable and modern looking way for this - without web browser and by using android account manager.
If you would like to use Google account and AccountManager to identify the user you can:
Get his token to Google Contacts (auth token type is "cp") through AccountManager on background thread:
public String getUserToken(Activity activity)
{
AccountManager accountManager = AccountManager.get(activity);
AccountManagerFuture<Bundle> amf = accountManager.getAuthTokenByFeatures("com.google", "cp", null, activity, Bundle.EMPTY, Bundle.EMPTY, null, null );
Bundle bundle = null;
try {
bundle = amf.getResult();
String name = (String) bundle.get(AccountManager.KEY_ACCOUNT_NAME);
String type = (String) bundle.get(AccountManager.KEY_ACCOUNT_TYPE);
String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
return token;
} catch (OperationCanceledException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (AuthenticatorException e) {
e.printStackTrace();
}
return null;
}
Pass received UserToken to the server over secured channel.
Validate the token at the server by google using gdata library (Google Data API library):
public String getUserId(String token)
{
ContactsService contactsService = new ContactsService("Taxi");
contactsService.setUserToken(token);
IFeed feed = null;
try {
feed = contactsService.getFeed(new URL("https://www.google.com/m8/feeds/contacts/default/full?max-results=10000"), ContactFeed.class);
} catch (IOException e) {
e.printStackTrace();
} catch (ServiceException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
if (feed == null)
return null;
String externalId = feed.getId();
IPerson person = feed.getAuthors().get(0);
String email = person.getEmail();
String name = person.getName();
String nameLang = person.getNameLang();
return externalId;
}
Google token can expire (usually after an hour), so if you failed to validate the token at the server, you must send response back to client, invalidate the token and get a new one. Use account manager to invalidate the token:
public void invalidateUserToken(Context context, String token)
{
AccountManager accountManager = AccountManager.get(context);
accountManager.invalidateAuthToken("com.google", token);
}
I think this blog post does exactly what you want. It worked for me. Both of the solutions posted here are viable and clever, but I think this does it exactly how the asker was asking.
Essentially, you're just getting an authToken using the "ah" scope, and passing it to the right webpage to get the ACSID cookie that will let you access any AppEngine page that uses UserService for authentication.
http://developer.android.com/search.html#q=AccountManager&t=0
http://developer.android.com/resources/samples/SampleSyncAdapter/index.html
at the bottom of this page you will find all needed code
best regards