5

I have an app that uses Google Cloud Endpoints. Some methods need authorization so I followed this tutorial. This requires the GET_ACCOUNTS permissions.

I am updating the app to work with runtime permissions. I do not like to request permission to read contacts, but GET_ACCOUNTS is in the same group. Because of this I am looking to use authorization without GET_ACCOUNTS permission.

I think that Google Sign In could work but I am unable to find a way to use the result from Google Sign In.

This is the code used to create the object to make the calls to the endpoint:

Helloworld.Builder helloWorld = new Helloworld.Builder(AppConstants.HTTP_TRANSPORT, AppConstants.JSON_FACTORY,credential);

The credential object must be a HttpRequestInitializer but from the Google Sign In I get a GoogleSignInAccount.

So, is it possible to do this? How should this be done?

peak
  • 72,551
  • 14
  • 101
  • 128
9and3r
  • 245
  • 1
  • 8

1 Answers1

6

I finally found the solution. Using the tutorial found here.

You must add the Client id in the GoogleSignInOptions:

 GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(CLIENT_ID)
            .requestEmail()
            .build();

Following the tutorial you will finaly get a GoogleSignInAccount. Set the token from the GoogleSignInAccount in a GoogleCredential object:

GoogleCredential credential = new GoogleCredential.Builder().setTransport(new NetHttpTransport())
            .setJsonFactory(JacksonFactory.getDefaultInstance())
            .build();
credential.setAccessToken(GoogleSignInAccount.getIdToken());

This credential is ready to make authenticated calls to Google Cloud Enpoints.

Note that you must remove "server:client_id:" part from the CLIENT_ID. So if you were using this:

credential = GoogleAccountCredential.usingAudience(this,
    "server:client_id:1-web-app.apps.googleusercontent.com");

Your CLIENT_ID would be:

CLIENT_ID = "1-web-app.apps.googleusercontent.com"

Also note that the token is valid for a limited amount of time (Aprox. 1 hour in my testing)

To avoid the 1 hour token limitation, use GoogleSignInApi.silentSignIn() to get a new token before every call you make to your endpoint. For example if you are not in the UI thread:

GoogleSignInOptions options = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).requestEmail()
                .requestIdToken(CLIENT_ID)
                .build();
GoogleSignInClient client = GoogleSignIn.getClient(context, options);
GoogleSignInAccount user = Tasks.await(getGoogleSignInClient(context).silentSignIn());

// Use the new user token as before 
GoogleCredential credential = new GoogleCredential.Builder().setTransport(new NetHttpTransport())
        .setJsonFactory(JacksonFactory.getDefaultInstance())
        .build();
credential.setAccessToken(user.getIdToken());
9and3r
  • 245
  • 1
  • 8
  • fantastic, i was JUST looking at this for the same reason as you (redoing permissions to support the runtime model and wanted to streamline), thanks a lot! i wonder if this also solves the issue on the google endpoints cloud side where you need to reload the authorized user as it doesn't have the id... – Creos Feb 28 '16 at 13:57
  • does not work for me: the backend receives a NULL com.google.appengine.api.users.User which happens when authorization has failed. did you face the same problem? thx – Creos Feb 29 '16 at 05:26
  • I edited the answer. Check if you have the CLIENT_ID set correctly. You have to remove "server:client_id:". I also found that the token expires so this will only work in a limited amount of time after sign in. – 9and3r Feb 29 '16 at 19:47
  • Thanks, i actually figured out the client id thing, my issue was more on the backend. Found this solution http://stackoverflow.com/questions/25365858/google-cloud-endpoints-and-users-authentication Frankly, it is insane that there is so little information on the endpoints doc site about the @authenticators feature https://cloud.google.com/appengine/docs/java/endpoints/javadoc/com/google/api/server/spi/config/Authenticator – Creos Feb 29 '16 at 23:49
  • oh wait, or are you saying you were able to get this to work using the default authenticator (ie you don't use the GoogleTokenIdVerifier explicitly on your server)? – Creos Feb 29 '16 at 23:51
  • Yes. I have never used any explicit authentificator code on the server. I simply add the user parameter to methods. I am currently using only the email of the user, so I do not know if ID is set or not (Related to your first comment). – 9and3r Mar 01 '16 at 00:03
  • very interesting! certainly doesn't work for me, i wonder what i'm doing wrong.... the user comes back as null, the same way as when you don't have authentication. BTW to your comment about using just the email, just be aware it's not a good idea to identify your users by email, best to use the getId of the GoogelSignInAccount as that's stable (you probably know that but thought I'd put it out there since you've been so helpful!) – Creos Mar 01 '16 at 00:11
  • sorry one last question: what happens when the token expires? do your backend call start throwing? is it easy to start a re-authentication flow (say the exceptions they throw have a resolution intent or result)? thanks – Creos Mar 01 '16 at 00:20
  • Thanks for the tip of the ID. I should change to use it. Currently I have no solution for the re-authentication. It seems that you can call the sign in intent in each call and the user input is only needed on the first time but I think is not a good solution so I am looking for a better solution. I used this tutorial to setup the Enpoints https://cloud.google.com/appengine/docs/java/endpoints/auth. One thing that It took me to figure is that the CLIENT_ID used in the android app is the one named WEB_CLIENT (Quite confusing). Apart from that I had no problems to set the default authenticator. – 9and3r Mar 01 '16 at 00:31
  • Thanks, i figured out what i was doing: erroneously passing the getId() instead of getTokenId()... I wish google used stronger types for the tokens, i made my own so i don't make that mistake again (ApiAuthToken :P). ya the WEB_CLIENT is confusing. On the using-email-as-id: ya please definitely look into it now before you save data, it is very painful to change later. It is possible for the email to change while the user remains the same, the only stable id is the googleSignInAccount.getId(). I think i might end up reauthenticating often because on each onStart i do a silent sign-in anyway... – Creos Mar 01 '16 at 00:50
  • I am getting and error in my appengine backend: WARNING 2016-06-19 23:56:18,928 users_id_token.py:404] Issuer was not valid: https:// accounts.google.com ERROR 2016-06-19 23:56:19,039 users_id_token.py:366] Token info endpoint returned status 400: Invalid Value – mparkes Jun 19 '16 at 23:57
  • Were you able to work around the re-authentication problem ? – Aalap Dec 01 '16 at 12:35
  • Unfortunately not. – 9and3r Dec 05 '16 at 17:49