Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google Contacts API - failing to refresh access token

We use Google Contacts API with OAuth2:

credential = new GoogleCredential.Builder().setTransport(new NetHttpTransport())
        .setJsonFactory(new JacksonFactory())
        .setClientSecrets(OAuth2ClientId(), OAuth2ClientSecret())
        .addRefreshListener(new CredentialRefreshListener() {...});

myService = new ContactsService("My-App");
myService.setOAuth2Credentials(credential);

and quite regularly we receive '401 Unauthorized' response that the GData library can't handle. AuthenticationException throws NPE when WWW-Authenticate header is missing.

Caused by: java.lang.NullPointerException: No authentication header information
        at com.google.gdata.util.AuthenticationException.initFromAuthHeader(AuthenticationException.java:96) ~[gdata-core-1.0-1.47.1.jar:na]
        at com.google.gdata.util.AuthenticationException.<init>(AuthenticationException.java:67) ~[gdata-core-1.0-1.47.1.jar:na]
        at com.google.gdata.client.http.HttpGDataRequest.handleErrorResponse(HttpGDataRequest.java:608) ~[gdata-core-1.0-1.47.1.jar:na]
        at com.google.gdata.client.http.GoogleGDataRequest.handleErrorResponse(GoogleGDataRequest.java:564) ~[gdata-core-1.0-1.47.1.jar:na]
        at com.google.gdata.client.http.HttpGDataRequest.checkResponse(HttpGDataRequest.java:560) ~[gdata-core-1.0-1.47.1.jar:na]
        at com.google.gdata.client.http.HttpGDataRequest.execute(HttpGDataRequest.java:538) ~[gdata-core-1.0-1.47.1.jar:na]
        at com.google.gdata.client.http.GoogleGDataRequest.execute(GoogleGDataRequest.java:536) ~[gdata-core-1.0-1.47.1.jar:na]
        at com.google.gdata.client.Service.getFeed(Service.java:1135) ~[gdata-core-1.0-1.47.1.jar:1.47.1]
        at com.google.gdata.client.Service.getFeed(Service.java:1077) ~[gdata-core-1.0-1.47.1.jar:1.47.1]
        at com.google.gdata.client.GoogleService.getFeed(GoogleService.java:676) ~[gdata-core-1.0-1.47.1.jar:1.47.1]
        at com.google.gdata.client.Service.query(Service.java:1237) ~[gdata-core-1.0-1.47.1.jar:1.47.1]
        at com.google.gdata.client.Service.query(Service.java:1178) ~[gdata-core-1.0-1.47.1.jar:1.47.1]

We managed to add a wrapper that would try token refreshing on this NPE. It helps but then there are still many cases when refreshing fails:

credential.refreshToken() == false

When we run refreshToken() in the debugger we see that executeRefreshToken() is executed without an exception but tokenResponse==null is returned. As a result refreshToken() returns false and no reason is passed to the listeners

try {
    TokenResponse tokenResponse = executeRefreshToken();
    if (tokenResponse != null) {
      setFromTokenResponse(tokenResponse);
      for (CredentialRefreshListener refreshListener : refreshListeners) {
        refreshListener.onTokenResponse(this, tokenResponse);
      }
      return true;
    }
  } catch (TokenResponseException e) {
    boolean statusCode4xx = 400 <= e.getStatusCode() && e.getStatusCode() < 500;
    // check if it is a normal error response
    if (e.getDetails() != null && statusCode4xx) {
      // We were unable to get a new access token (e.g. it may have been revoked), we must now
      // indicate that our current token is invalid.
      setAccessToken(null);
      setExpiresInSeconds(null);
    }
    for (CredentialRefreshListener refreshListener : refreshListeners) {
      refreshListener.onTokenErrorResponse(this, e.getDetails());
    }
    if (statusCode4xx) {
      throw e;
    }
  }
  return false;

Our tokens are always for multiple scopes: https://www.googleapis.com/auth/userinfo.email https://www.google.com/m8/feeds https://www.googleapis.com/auth/calendar https://mail.google.com/ https://www.googleapis.com/auth/tasks

Update: We've successfully moved to People API and the new API is also used by our Unified API Contacts API https://docs.aurinko.io/article/25-contacts-api

like image 310
Alexey Avatar asked Oct 09 '14 19:10

Alexey


People also ask

Does Google API refresh token expire?

The refresh token is set with a very long expiration time of 200 days. If the traffic to this API is 10 requests/second, then it can generate as many as 864,000 tokens in a day.

How do I refresh my access token?

To refresh the access token, select the Refresh access token API call within the Authorization folder of the Postman collection. Next, click the Send button to request a new access_token .


1 Answers

There is currently a bug in the Contacts API where certain HTTP User-Agent strings, cause the 401 response to return as an HTML page instead of an XML response, and missing the WWW-Authenticate header that the AuthenticationException class is relying on. GContacts-Java is one of these special user agent strings. A workaround is to change the user agent of your client after you create it:

ContactsService service = new ContactsService(applicationName);
service.getRequestFactory().setHeader("User-Agent", applicationName);

This should eliminate the NPE, and allow the client library to automatically detect expired tokens and refresh them automatically.

like image 69
Eric Koleda Avatar answered Sep 29 '22 14:09

Eric Koleda