'How can I send an e-mail from Microsoft Outlook using MSAL in Java?

I'm trying to develop an application in Java / Spring that connects to an Microsoft Outlook / Exchange SMTP Relay to send e-mails using MSAL4J, but I keep getting an authentication error when I try to connect to the mail server.

Am I doing anything wrong here?

package com.email;

import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.IAuthenticationResult;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.net.MalformedURLException;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class SmtpTestMSAL {

    private static final String clientId = "<removed>";
    private static final String tenantId = "<removed>";
    private static final String secret = "<removed>";
    private static final String to = "<removed>";
    private static final String from = "<removed>";
    private static final String pwd = "<removed>";

    private static final String authUrl = "https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/authorize";
    private static final Set<String> scope = Set.of("https://graph.microsoft.com/.default");

    public static void main(String[] args) throws MalformedURLException, ExecutionException, InterruptedException {
        String token = getAccessToken();

        System.out.println("token: " + token);

        JavaMailSender mailSender = prepareSender(token);
        SimpleMailMessage message = new SimpleMailMessage();

        message.setFrom(from);
        message.setTo(to);
        message.setSubject("testing modern auth");
        message.setText("testing modern auth");

        mailSender.send(message);
    }

    private static String getAccessToken() throws MalformedURLException, ExecutionException, InterruptedException {
        ConfidentialClientApplication app = ConfidentialClientApplication
                .builder(
                        clientId,
                        ClientCredentialFactory.createFromSecret(secret)
                )
                .authority(authUrl)
                .build();

        ClientCredentialParameters clientCredentialParam = ClientCredentialParameters
                .builder(scope)
                .build();

        CompletableFuture<IAuthenticationResult> future = app.acquireToken(clientCredentialParam);

        return future.get().accessToken();
    }

    private static JavaMailSender prepareSender(String oauthToken) {

        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        Properties props = mailSender.getJavaMailProperties();

        mailSender.setHost("smtp.office365.com");
        mailSender.setPort(587);
        mailSender.setUsername(from);
        mailSender.setPassword(pwd);

        props.put("mail.debug", "true");
        props.put("mail.debug.auth", "true");
        props.put("mail.smtp.starttls.required", "true");
        props.put("mail.smtp.sasl.enable", "true");
        props.put("mail.smtp.sasl.mechanisms", "XOAUTH2");
        props.put("mail.smtp.sasl.mechanisms.oauth2.oauthToken", oauthToken);
        props.put("mail.transport.protocol", "smtp");

        return mailSender;
    }
}

this is the log of the SMTP session:

DEBUG: Jakarta Mail version 1.6.7
DEBUG: successfully loaded resource: /META-INF/javamail.default.providers
DEBUG: Tables of loaded providers
DEBUG: Providers Listed By Class Name: {com.sun.mail.smtp.SMTPTransport=javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle], com.sun.mail.imap.IMAPSSLStore=javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle], com.sun.mail.pop3.POP3Store=javax.mail.Provider[STORE,pop3,com.sun.mail.pop3.POP3Store,Oracle], com.sun.mail.smtp.SMTPSSLTransport=javax.mail.Provider[TRANSPORT,smtps,com.sun.mail.smtp.SMTPSSLTransport,Oracle], com.sun.mail.imap.IMAPStore=javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Oracle], com.sun.mail.pop3.POP3SSLStore=javax.mail.Provider[STORE,pop3s,com.sun.mail.pop3.POP3SSLStore,Oracle]}
DEBUG: Providers Listed By Protocol: {imap=javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Oracle], smtp=javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle], pop3=javax.mail.Provider[STORE,pop3,com.sun.mail.pop3.POP3Store,Oracle], imaps=javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle], smtps=javax.mail.Provider[TRANSPORT,smtps,com.sun.mail.smtp.SMTPSSLTransport,Oracle], pop3s=javax.mail.Provider[STORE,pop3s,com.sun.mail.pop3.POP3SSLStore,Oracle]}
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
DEBUG SMTP: enable SASL
DEBUG SMTP: useEhlo true, useAuth false
DEBUG SMTP: trying to connect to host "smtp.office365.com", port 587, isSSL false
220 <removed>.outlook.office365.com Microsoft ESMTP MAIL Service ready at Fri, 13 May 2022 13:36:12 +0000
DEBUG SMTP: connected to host "smtp.office365.com", port: 587
EHLO pc-364.home
250-<removed>.outlook.office365.com Hello [37.135.92.227]
250-SIZE 157286400
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-STARTTLS
250-8BITMIME
250-BINARYMIME
250-CHUNKING
250 SMTPUTF8
DEBUG SMTP: Found extension "SIZE", arg "157286400"
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "DSN", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "BINARYMIME", arg ""
DEBUG SMTP: Found extension "CHUNKING", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
STARTTLS
220 2.0.0 SMTP server ready
EHLO pc-364.home
250-PAZP264CA0194.outlook.office365.com Hello [37.135.92.227]
250-SIZE 157286400
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-AUTH LOGIN XOAUTH2
250-8BITMIME
250-BINARYMIME
250-CHUNKING
250 SMTPUTF8
DEBUG SMTP: Found extension "SIZE", arg "157286400"
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "DSN", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN XOAUTH2"
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "BINARYMIME", arg ""
DEBUG SMTP: Found extension "CHUNKING", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
DEBUG SMTP: protocolConnect login, host=smtp.office365.com, user=<removed>, password=<non-null>
DEBUG SMTP: Authenticate with SASL
DEBUG SMTP: SASL mechanisms allowed: XOAUTH2
DEBUG SMTP: SASL Mechanisms:
DEBUG SMTP:  XOAUTH2
DEBUG SMTP: 
DEBUG SMTP: SASL client XOAUTH2
DEBUG SMTP: SASL callback length: 2
DEBUG SMTP: SASL callback 0: javax.security.auth.callback.NameCallback@1188e820
DEBUG SMTP: SASL callback 1: javax.security.auth.callback.PasswordCallback@2f490758
AUTH XOAUTH2 <removed>
535 5.7.3 Authentication unsuccessful [<removed>.OUTLOOK.COM]
DEBUG SMTP: SASL authentication failed

Any help is super appreciated!



Solution 1:[1]

Ok, I've managed to make it work using Microsoft Graph API instead of MSAL, posting an example here in case anyone ever needs it:

package com.email;

import com.azure.identity.UsernamePasswordCredential;
import com.azure.identity.UsernamePasswordCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.models.BodyType;
import com.microsoft.graph.models.EmailAddress;
import com.microsoft.graph.models.ItemBody;
import com.microsoft.graph.models.Message;
import com.microsoft.graph.models.Recipient;
import com.microsoft.graph.models.UserSendMailParameterSet;
import com.microsoft.graph.requests.GraphServiceClient;
import okhttp3.Request;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Set;

public class GraphAPIExample {

    private static final String clientId = System.getenv("SMTP_CLIENT_ID");
    private static final String to = "[email protected]";
    private static final String mailbox = System.getenv("SMTP_MAILBOX");
    private static final String pwd = System.getenv("SMTP_MAILBOX_PWD");

    private static final Set<String> scopes = Set.of("Mail.Send");

    public static void main(String[] args) {
        final UsernamePasswordCredential usernamePasswordCredential = new UsernamePasswordCredentialBuilder()
                .clientId(clientId)
                .username(mailbox)
                .password(pwd)
                .build();

        final TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(new ArrayList<>(scopes), usernamePasswordCredential);

        final GraphServiceClient<Request> graphClient =
                GraphServiceClient
                        .builder()
                        .authenticationProvider(tokenCredentialAuthProvider)
                        .buildClient();

        graphClient.me()
                .sendMail(UserSendMailParameterSet
                        .newBuilder()
                        .withMessage(createMessage())
                        .build())
                .buildRequest()
                .post();
    }

    private static Message createMessage() {
        Message message = new Message();
        ItemBody body = new ItemBody();

        message.subject = "testing modern auth";

        body.contentType = BodyType.TEXT;
        body.content = "testing modern auth";
        message.body = body;

        LinkedList<Recipient> toRecipientsList = new LinkedList<>();
        Recipient toRecipients = new Recipient();
        EmailAddress emailAddress = new EmailAddress();
        emailAddress.address = to;
        toRecipients.emailAddress = emailAddress;
        toRecipientsList.add(toRecipients);
        message.toRecipients = toRecipientsList;

        return message;
    }
}

You're going to need the following dependencies to run this example:

implementation("com.microsoft.graph:microsoft-graph:5.23.0")
implementation("com.azure:azure-identity:1.5.1")

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 felipecao