0

Following the official Oauth example using Google API Client Libraries here, I wrote my application that obtains user consent on startup, and then keeps sending notifications via email. It works for some hours, and then suddenly fails with

com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request POST https://oauth2.googleapis.com/token { "error": "invalid_grant", "error_description": "Token has been expired or revoked." }

I have also set offline access type and force approval prompt as mentioned here but it never made a difference.

One peculiarity about my usecase is that I have multiple applications sharing the same credential store. I did this so that the creds stored by one app can seamlesslessly be used by another app without re-prompting the user (and all this being code for purely personal use on my desktop). With this setup, even as one application is able to send emails, another application would sometimes give the error I mentioned about.

Here is my code.

private Gmail createGmailService() throws IOException, GeneralSecurityException {
    NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    JsonFactory jsonFactory = GsonFactory.getDefaultInstance();

    Credential credentials = getCredentials(httpTransport, jsonFactory);

    return new Gmail.Builder(httpTransport, jsonFactory, credentials)
            .setApplicationName(gCloudApplicationName)
            .build();
}

public Gmail getGmailService() {
    return gmailService;
}

/**
 * Creates an authorized Credential object.
 *
 * @param httpTransport The network HTTP Transport.
 * @param jsonFactory   JSON factory
 * @return An authorized Credential object.
 * @throws IOException If the credentials.json file cannot be found.
 */
private Credential getCredentials(final NetHttpTransport httpTransport, JsonFactory jsonFactory) throws IOException {
    GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(jsonFactory, new FileReader(credentialsFilePath));

    List<String> scopes = List.of(GmailScopes.GMAIL_SEND);

    // Build flow and trigger user authorization request.
    GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
            httpTransport, jsonFactory, clientSecrets, scopes)
            .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(tokensDirectoryPath)))
            .setAccessType("offline")
            // https://stackoverflow.com/questions/13777842/how-to-get-offline-token-and-refresh-token-and-auto-refresh-access-to-google-api
            .setApprovalPrompt("force")
            .build();

    LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
    return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}

I can't make sense of what could be going on. Any help is much appreciated.

Walking Corpse
  • 107
  • 7
  • 31
  • 49
  • Please edit your question and include your code. How long is some hours? Is it seven days exactly? are you storing the latest refresh token each time? How are you storing the refresh token – Linda Lawton - DaImTo May 05 '23 at 16:41
  • Updated code, I'm using a FileDataStoreFactory to store the received token. I didn't write any code to store the latest refresh token each, that is supposed to be handled by the Google Client API library. I don't think it is 7 days, probably only a day, I'm not really sure. – Walking Corpse May 05 '23 at 20:29
  • I believe you need to get new authentication token using refresh token. when you get the error above `"Token has been expired or revoked."` try to call credentials.refreshToken(). [Credentials docs](https://cloud.google.com/java/docs/reference/google-oauth-client/latest/com.google.api.client.auth.oauth2.Credential#com_google_api_client_auth_oauth2_Credential_refreshToken__) – Vlad Ulshin May 06 '23 at 00:11
  • 1
    check token directory do you have only one file? the way file data store works I think you may have one per application – Linda Lawton - DaImTo May 07 '23 at 08:32
  • When I caught the 401 Unauthorized error (TokenResponseException) and retried sending email after calling credential.refreshToken(); and it still fails with the same error. I'm using the same token directory for 2 different applications. From Linda's reply it looks like one application may continue to work but not the other? And what's what seems to be happening also. I will try with separate token directory per application. – Walking Corpse Jun 09 '23 at 13:24

0 Answers0