Connecting to Exchange 365 via SMTP OAuth

2 days ago 4
ARTICLE AD BOX

For sending emails on a server application without user interaction I am trying to replace my basic auth connection to Exchange 365 with an OAuth 2.0 connection.

I know that there is an alternative way to send emails via Exchange 365 by using Microsoft Graph, but because of the limitations of Graph's API like defining at max 5 custom headers, I cannot use Graph.

On the Exchange configuration side I created an app application and tried out all different combinations of Application and Delegated API permissions (Mail. and SMTP.SendAsApp) for Office 365 Exchange Online.

I also configured this via the Exchange Powershell: Set-CASMailbox -Identity <my-senders-email> -SmtpClientAuthenticationDisabled $false.

When trying out the following code, I always get this error: Exception in thread "main" jakarta.mail.AuthenticationFailedException: 535 5.7.3 Authentication unsuccessful.

Currently I am not sure if I forgot to configure something in Exchange or if the Java code is incorrect. Maybe someone of you already solved this problem?

package com.example.dev; import jakarta.mail.Message; import jakarta.mail.Session; import jakarta.mail.Transport; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; import tools.jackson.databind.ObjectMapper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Properties; public class Main { private static final String FROM_USER = ""; private static final String TO_USER = ""; private static final String CLIENT_ID = ""; private static final String TENANT_ID = ""; private static final String CLIENT_SECRET = ""; static void main() throws Exception { sendMimeMessage(createMimeMessage()); } public static MimeMessage createMimeMessage() throws Exception { Properties props = new Properties(); Session session = Session.getInstance(props); MimeMessage mimeMessage = new MimeMessage(session); mimeMessage.setFrom(new InternetAddress(FROM_USER)); mimeMessage.setRecipients(Message.RecipientType.TO, InternetAddress.parse(TO_USER)); mimeMessage.setSubject("My Test Subject"); mimeMessage.setText("My Test Mail Content"); mimeMessage.saveChanges(); return mimeMessage; } public static void sendMimeMessage(MimeMessage mimeMessage) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); mimeMessage.writeTo(baos); Properties props = new Properties(); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.starttls.enable", "true"); props.put("mail.smtp.host", "smtp.office365.com"); props.put("mail.smtp.port", "587"); props.put("mail.smtp.auth.mechanisms", "XOAUTH2"); props.put("mail.smtp.auth.login.disable", "true"); props.put("mail.smtp.auth.plain.disable", "true"); Session session = Session.getInstance(props); session.setDebug(true); String smtpUser = mimeMessage.getFrom()[0].toString(); String accessToken = getAccessToken(); Transport transport = session.getTransport("smtp"); transport.connect("smtp.office365.com", smtpUser, accessToken); transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients()); transport.close(); } private static String getAccessToken() throws IOException { String tokenUrl = "https://login.microsoftonline.com/" + TENANT_ID + "/oauth2/v2.0/token"; String body = "client_id=" + CLIENT_ID + "&client_secret=" + CLIENT_SECRET + "&scope=https%3A%2F%2Foutlook.office365.com%2F.default" + "&grant_type=client_credentials"; HttpURLConnection con = (HttpURLConnection) new URL(tokenUrl).openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); con.setDoOutput(true); con.getOutputStream().write(body.getBytes(StandardCharsets.UTF_8)); int responseCode = con.getResponseCode(); if (responseCode != 200) { String errorResponse = new String(con.getErrorStream().readAllBytes(), StandardCharsets.UTF_8); throw new IOException("Token request failed with HTTP " + responseCode + ": " + errorResponse); } String response = new String(con.getInputStream().readAllBytes(), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map<String, Object> json = mapper.readValue(response, Map.class); return (String) json.get("access_token"); } }
Read Entire Article