Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google OAuth2 - access token & refresh token -> invalid_grant/Code was already redeemed

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.

like image 655
Markymark Avatar asked Mar 15 '15 08:03

Markymark


1 Answers

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;
    }
}
like image 69
Markymark Avatar answered Oct 24 '22 13:10

Markymark