1

I try to send mail use Gmail API. My code almost the same like in google tutorial, but when I invoice this send Message method I get error Request had insufficient authentication scopes.

This is full stacktrace of error:

POST https://gmail.googleapis.com/gmail/v1/users/example.com@gmail.com/messages/send
{
  "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"
}
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
POST https://gmail.googleapis.com/gmail/v1/users/example.com@gmail.com/messages/send
{
  "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"
}

This is my code:

public class GmailService {
    /**
     * Application name.
     */
    private static final String APPLICATION_NAME = "example.com";
    private static final String EXAMPLE_EMAIL = "example.com@gmail.com";
    /**
     * Global instance of the JSON factory.
     */
    private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
    /**
     * Directory to store authorization tokens for this application.
     */
    private static final String TOKENS_DIRECTORY_PATH = "tokens";

    /**
     * Global instance of the scopes required by this quickstart.
     * If modifying these scopes, delete your previously saved tokens/ folder.
     */
    private static final List<String> SCOPES = new ArrayList<>();
    private static final String CREDENTIALS_FILE_PATH = "/credentials.json";

    /**
     * Creates an authorized Credential object.
     *
     * @param HTTP_TRANSPORT The network HTTP Transport.
     * @return An authorized Credential object.
     * @throws IOException If the credentials.json file cannot be found.
     */
    private Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
        SCOPES.add(GmailScopes.GMAIL_LABELS);
        SCOPES.add(GmailScopes.GMAIL_SEND);
        SCOPES.add(GmailScopes.MAIL_GOOGLE_COM);
        // Load client secrets.
        InputStream in = GmailService.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
        if (in == null) {
            throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
        }
        final GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

        // Build flow and trigger user authorization request.
        final 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 boolean sendMessage(String recipientAddress, String subject, String body) throws MessagingException,
            IOException, GeneralSecurityException {

        Message message = createMessageWithEmail(
                createEmail(recipientAddress, EXAMPLE_EMAIL, subject, body));

        return createGmail().users()
                .messages()
                .send(EXAMPLE_EMAIL, message)
                .execute()
                .getLabelIds().contains("SENT");
    }

    private MimeMessage createEmail(String to, String from, String subject, String bodyText) throws MessagingException {
        MimeMessage email = new MimeMessage(Session.getDefaultInstance(new Properties(), null));
        email.setFrom(new InternetAddress(from));
        email.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(to));
        email.setSubject(subject);
        email.setText(bodyText);
        return email;
    }

    private Message createMessageWithEmail(MimeMessage emailContent) throws MessagingException, IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        emailContent.writeTo(buffer);

        return new Message()
                .setRaw(Base64.encodeBase64URLSafeString(buffer.toByteArray()));
    }

    private Gmail createGmail() throws IOException, GeneralSecurityException {

        // Build a new authorized API client service.
        final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
        return new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
                .setApplicationName(APPLICATION_NAME)
                .build();
    }
}

What I neet to do?

I use OAuth 2.0 Client IDs credential:

{
  "web": {
    "client_id": "[REDACTED].googleusercontent.com",
    "project_id": "example-3523409",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_secret": "[REDACTED]",
    "redirect_uris": [
      "http://localhost:8888/Callback",
    ],
    "javascript_origins": [
      "http://localhost:4200",
      "http://localhost:8888",
      "http://localhost:8080"
    ]
  }
}

It looks like your post is mostly code; please add some more details.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • Might help to understand what is going on: https://stackoverflow.com/questions/70099753/access-token-scope-insufficient-403-request-had-insufficient-authentication-scop –  Jul 27 '22 at 13:36
  • 1
    Never, ever post your credentials. –  Jul 27 '22 at 13:37
  • This wrong credentials, I changed before post) – Aliaksandr Vysotskiy Jul 27 '22 at 13:40
  • @AliaksandrVysotskiy just make sure that you request a new secret on the project. You are using Localhost redirect uri which would mean anyone could use your client. Its against Googles TOS for you to share the client id and client secret. – Linda Lawton - DaImTo Jul 27 '22 at 15:41

1 Answers1

0

The Gmail.send method requires that the user authencation the application with one of the following scopes in order to run

enter image description here

The error message Request had insufficient authentication scopes. means that the currently authencation user has not authorized the application with one of those scopes.

In your code i can see that you have added

SCOPES.add(GmailScopes.GMAIL_LABELS);
SCOPES.add(GmailScopes.GMAIL_SEND);
SCOPES.add(GmailScopes.MAIL_GOOGLE_COM);

the Gmail_send scope should be enough.

However your code is storing user tokens in TOKENS_DIRECTORY_PATH. Which makes me thing that you previously authorized this user with a reduced scope. Then you added the new scopes ran your application again. However with the authorization tokens being stored in TOKENS_DIRECTORY_PATH your application is still picking up the old user consent and the old scopes.

Solution is to delete what ever is in TOKENS_DIRECTORY_PATH and run your application again the consent screen should pop up and request authorization of the user again. This time including the proper scopes.

Note web app:

You appear to be using web app credentials yet the code you are using is for AuthorizationCodeInstalledApp. This code may not work if you try hosting it on a web server.

Unless you are using JavaScript you don't need to set the javascript_origins in your credentials.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • Thanks a lot for answer (You my hero). You write: You appear to be using web app credentials yet the code you are using is for AuthorizationCodeInstalledApp. This code may not work if you try hosting it on a web server, but i want to put it on server, how to do it? Can you give some example or link to tutorial, please. – Aliaksandr Vysotskiy Jul 27 '22 at 16:49
  • I'm not sure i have a Java web example on hand. But i can look around tomorrow. – Linda Lawton - DaImTo Jul 27 '22 at 17:04
  • try this https://github.com/googleapis/google-api-java-client/blob/main/docs/oauth-2.0.md#web-server-applications – Linda Lawton - DaImTo Jul 28 '22 at 12:34
  • protected AuthorizationCodeFlow initializeFlow() throws IOException { return new GoogleAuthorizationCodeFlow.Builder( new NetHttpTransport(), GsonFactory.getDefaultInstance() "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]", Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory( DATA_STORE_FACTORY).setAccessType("offline").build(); } – Aliaksandr Vysotskiy Jul 31 '22 at 13:14
  • This is almost the same code which i use, does it? – Aliaksandr Vysotskiy Jul 31 '22 at 13:15