6

My goal is to have some kind of long lived access token so that my Android app can read the events of a user's Google Calendar for the day without requiring user approval every time.

I am able to generate -- what I think is -- a one-time authorization code; however, when I send this to my server side, app engine, I get the following error response:

400 OK { "error" : "invalid_grant", "error_description" : "Code was already redeemed." }

That is the exception that is being thrown. I'm just catching it and sending it back to myself as a way of debugging.

The one-time code I get starts with 4/VUr so I assume it is a one-time code and not a regular access token.

Currently, on Android, I allow a user to sign in using Google+ so that I have their email address. From there I request a one-time authorization code with the following code:

try {
    Bundle appActivities = new Bundle();
    appActivities.putString(GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES, "http://schemas.google.com/AddActivity");
    String scopes = "oauth2:server:client_id:" + Constants.SERVER_CLIENT_ID + ":api_scope:https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/calendar";
    //Plus.SCOPE_PLUS_LOGIN + " " + CalendarScopes.CALENDAR_READONLY;
    String acctName = "myGmail";
    String token = GoogleAuthUtil.getToken(getApplicationContext(), acctName, scopes, appActivities);

} catch (UserRecoverableAuthException e) {
        startActivityForResult(e.getIntent(), 257/*REQUEST_AUTHORIZATION/*/);
} catch (Exception e) {
        e.printStackTrace();
}

This code is from here and it seems that this is what I must do.

I then send this code to my App Engine Endpoint. I use code from here to request a access and refresh token.

The following is the code that I use as a simple test:

HttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory(); 
//ArrayList<String> scopes = new ArrayList<>();
//scopes.add("https://www.googleapis.com/auth/plus.login");
//scopes.add("https://www.googleapis.com/auth/calendar");

GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest(transport, jsonFactory,
                    SERVER_CLIENT_ID, SERVER_CLIENT_SECRET, code, "postmessage")/*.setScopes(scopes)*/.execute();

            //urn:ietf:wg:oauth:2.0:oob
            //postmessage
code = tokenResponse.getRefreshToken();

It is failing right when I instantiate GoogleAuthorizationCodeTokenRequest

To name a few I have seen https://developers.google.com/accounts/docs/CrossClientAuth#offlineAccess
Google-api-php Refresh Token returns invalid_grant
getting Google oauth authorization token from Android- return with invalid_scope/ Unknown error

Setting the redirect uri differently did not work. I did fill out the consent screen for my app engine project. Both installed Android client id and web application client id are in the same project. I have the redirect uri for the web application set to xxxxxxxx.appspot.com for my app.

Gradle for my main app:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    //    compile 'com.google.api-client:google-api-client:1.18.0-rc'
    compile 'com.android.support:appcompat-v7:21.0.3'
    compile 'org.altbeacon:android-beacon-library:2.1.3'
    compile 'com.google.apis:google-api-services-calendar:v3-rev118-1.19.1'
    compile 'com.google.api-client:google-api-client-android:1.18.0-rc'
    compile 'com.google.android.gms:play-services:6.5.87'
    compile 'com.google.http-client:google-http-client-jackson:1.19.0'
    compile project(path: ':beaconBackend', configuration: 'android-endpoints')
}

Gradle for my backend:

dependencies {
    appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.14'
    compile 'com.google.appengine:appengine-endpoints:1.9.14'
    compile 'com.google.appengine:appengine-endpoints-deps:1.9.14'
    compile 'javax.servlet:servlet-api:2.5'
}

Any help would really be appreciated! Thanks!

Also, note that I have tried invalidating/revoking the current access token (or one-time code?).

I just need a way to have some kind of long living access token without user interaction after the first time.

Community
  • 1
  • 1
Markymark
  • 1,724
  • 22
  • 29
  • "Code was already redeemed." error is thrown when you are trying to use an authorization code that has already been used. The given authorization code can be used only once. Regarding your second questions, if you don't want the user to be prompted with the consent screen once he/she authorizes your app then go with offline access https://developers.google.com/accounts/docs/OAuth2WebServer#offline – SGC Mar 16 '15 at 20:24
  • Thank you for your comment and the link, SGC. I am attempting to get offline access using an authorization code so that I can get a refresh and access token. Perhaps my problem is that I am not correctly invalidating the current authorization code that I have. It looks like the link you gave does the same thing but uses REST rather than the API libraries I am using. – Markymark Mar 16 '15 at 20:33
  • I tried resetting my client_secret and did get a new authorization code but still got the same error :/ so it must be the way that I am requesting the access/refresh token – Markymark Mar 16 '15 at 21:10
  • How did you try invalidating the current one-time code ? – sacha Mar 19 '15 at 16:36
  • any answer for this question in current time ? – Prafulla Kumar Sahu Nov 17 '15 at 14:14
  • @PrafullaKumarSahu, yes, I'll update the answer after work today. – Markymark Nov 17 '15 at 17:46
  • @Marky17 that will be really helpful. – Prafulla Kumar Sahu Nov 17 '15 at 17:50
  • @PrafullaKumarSahu please see answer below. I can try to help if you still have problems. This ended up at least being the answer for me. – Markymark Nov 18 '15 at 02:37

1 Answers1

2

It's been a while since I worked on this project -- it was no longer needed.

The problem was within the server side app. I just wasn't requesting it correctly. Once my server side app (App Engine Endpoint) received the refresh token from the Android app I did the following on the server side.

private String refreshAccessToken(String refreshToken, String clientId, String clientSecret) throws IOException {
    try {
        TokenResponse response =
                new GoogleRefreshTokenRequest(new NetHttpTransport(), new JacksonFactory(),
                        refreshToken, clientId, clientSecret).execute();
        return response.getAccessToken();
    } catch (TokenResponseException e) {
        return null;
    }
}
Markymark
  • 1,724
  • 22
  • 29
  • http://stackoverflow.com/questions/33754177/google-analytics-custom-plugin-getting-error-invalid-grant this is my actual problem, but trying get idea from yourcase. – Prafulla Kumar Sahu Nov 18 '15 at 02:44