Search code examples
javagoogle-apigoogle-oauthgmail-apigoogle-api-java-client

Reading labels works but reading messages results in ACCESS_TOKEN_SCOPE_INSUFFICIENT


I have created one poc using Gmail API which read all email and print on console. I have take refrence from Gmail api Java quickstart.

I have follows all the steps like create project, Gmail API enable, OAuth credentials in google cloud console

The problem is, when I run the code all the labels are printed successfully but I'm not able to read mail messages. Some an error are getting which are below:

> Task :GmailQuickstart.main()
Labels:
- CHAT
- SENT
- INBOX
- IMPORTANT
- TRASH
- DRAFT
- SPAM
- CATEGORY_FORUMS
- CATEGORY_UPDATES
- CATEGORY_PERSONAL
- CATEGORY_PROMOTIONS
- CATEGORY_SOCIAL
- STARRED
- UNREAD
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
GET https://gmail.googleapis.com/gmail/v1/users/me/messages
{
  "code": 403,
  "details": [
    {
      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
      "reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT"
    }
  ],
  "errors": [
    {
      "domain": "global",
      "message": "Insufficient Permission",
      "reason": "insufficientPermissions"
    }
  ],
  "message": "Request had insufficient authentication scopes.",
  "status": "PERMISSION_DENIED"
}
    at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:146)
    at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:118)
    at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:37)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:439)
    at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1111)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:525)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:466)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:576)
    at GmailQuickstart.main(GmailQuickstart.java:80)

> Task :GmailQuickstart.main() FAILED

Execution failed for task ':GmailQuickstart.main()'.
> Process 'command '/home/bhautik/Downloads/jdk-11.0.15.1_linux-x64_bin/data/usr/lib/jvm/jdk-11/bin/java'' finished with non-zero exit value 1

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

And my code was below:

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.gmail.Gmail;
import com.google.api.services.gmail.GmailScopes;
import com.google.api.services.gmail.model.Label;
import com.google.api.services.gmail.model.ListLabelsResponse;
import com.google.api.services.gmail.model.ListMessagesResponse;
import com.google.api.services.gmail.model.Message;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/* class to demonstrate use of Gmail list labels API */
public class GmailQuickstart {

    private static final String APPLICATION_NAME = "Gmail API Java Quickstart";
    private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
    private static final String TOKENS_DIRECTORY_PATH = "tokens";
    private static final List<String> SCOPES = Collections.singletonList(GmailScopes.GMAIL_LABELS);
    private static final String CREDENTIALS_FILE_PATH = "/credentials.json";
    private static final String USER_ID = "me";

    private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT)
            throws IOException {
        // Load client secrets.
        InputStream in = GmailQuickstart.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
        if (in == null) {
            throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
        }
        GoogleClientSecrets clientSecrets =
                GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

        // Build flow and trigger user authorization request.
        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
                HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
                .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
                .setAccessType("offline")
                .build();
        LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
        Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
        //returns an authorized Credential object.
        return credential;
    }

    public static void main(String... args) throws IOException, GeneralSecurityException {
        // Build a new authorized API client service.
        final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
        Gmail service = new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
                .setApplicationName(APPLICATION_NAME)
                .build();

        // Print the labels in the user's account.
        String user = "me";
        ListLabelsResponse listResponse = service.users().labels().list(user).execute();
        List<Label> labels = listResponse.getLabels();
        if (labels.isEmpty()) {
            System.out.println("No labels found.");
        } else {
            System.out.println("Labels:");
            for (Label label : labels) {
                System.out.printf("- %s\n", label.getName());
            }
        }

        // Print the message
        ListMessagesResponse response = service.users().messages().list(USER_ID).execute();
//        List<Message> messages = response.getMessages();
        List<Message> messages = new ArrayList<Message>();
        while (response.getMessages() != null) {
            messages.addAll(response.getMessages());
            if (response.getNextPageToken() != null) {
                String pageToken = response.getNextPageToken();
                response = service.users().messages().list(USER_ID).setPageToken(pageToken).execute();
            } else {
                break;
            }
        }

        for (Message message : messages) {
            System.out.println(message.getId());
            System.out.println(message.getPayload());
            Message test = service.users().messages().get("me", message.getId()).execute();
            System.out.println(test.getSnippet());
        }
    }
}

Solution

  • ACCESS_TOKEN_SCOPE_INSUFFICIENT is a very common error message. It comes from copying the example without understanding what its doing. This is googles fault for not explaining things better.

    The quick start uses the lables.list method. This method runs on users private data so you used Oauth2 to request permission of the user to access their data.

    The method can run with any of the following permissions being requested

    enter image description here

    Best practice is to only request the permissions you need. So the code is asking for the GmailScopes.GMAIL_LABELS permission which will only give you access to see the lables

    Now to read a users messages the messges.listmethod you need to request permission with one of the following scopes

    enter image description here

    as you can see the label scope is not there. Thats because you need a higher level of permissions to access this data..

    Solution:

    Change the scope in your code to request one of the scopes needed for messgaes.list.

    Then you need to reauthorize your application. YOu can do this in a few was.

    1. delete the file found in TOKENS_DIRECTORY_PATH
    2. change .authorize("user"); the text passed here to something else.

    When you run your app again it should prompt you for authorization again.