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

Gmail Send request results in "insufficient authentication scopes" error


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/[email protected]/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/[email protected]/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 = "[email protected]";
    /**
     * 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.


Solution

  • 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.