Question

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:

  1. Client ID for web application (for my rails backend)

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

Was it helpful?

Solution

Two things solved this for me:

  1. Make sure the Android and Web Client IDs are setup in correctly in the same Google Cloud Console project.

  2. 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";

OTHER TIPS

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"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top