mario
mario

Reputation: 71

Java Oauth2 send email using Office 365

I'm using Java 1.8, Jakarta email (version 2.1.0) to connect to Microsoft 365 using OAuth for authentication with SMTP. Client has required to use Oauth authenticate, not basic smtp authentification. After reading of documentation https://learn.microsoft.com/en-us/exchange/client-developer... and lots other resources I have configured AD to this permissions:

Microsoft Graph    offline_access
Microsoft Graph    User.Read
Microsoft Graph    Mail.Send
Microsoft Graph    openid
Microsoft Graph    IMAP.AccessAsUser.All
Microsoft Graph    SMTP.Send

Office 365 Exchange Online    full_access_as_app
Office 365 Exchange Online    POP.AccessAsApp
Office 365 Exchange Online    Mail.Send
Office 365 Exchange Online    IMAP.AccessAsApp

Activated SMTP client authentication using PowerShell on Azure https://learn.microsoft.com/en-us/exchange/clients...

PS C:\Users\dx-2102> Get-TransportConfig | Format-List SmtpClientAuthenticationDisabled   
SmtpClientAuthenticationDisabled : False

implemented code for send email (Java):

Properties prop = new Properties();
prop.put("mail.smtp.auth", "true");
prop.put("mail.smtp.starttls.enable", "true");
prop.put("mail.smtp.host", emailSettings.getSmtp().getHostname());
prop.put("mail.smtp.port", emailSettings.getSmtp().getPort());
prop.put("mail.debug", "true");
prop.put("mail.debug.auth", "true");
prop.put("mail.smtp.auth.xoauth2.disable", "false");
prop.put("mail.smtp.auth.mechanisms", "XOAUTH2");
prop.put("mail.transport.protocol", "smtp");
prop.put("mail.smtp.auth.login.disable", "true");
prop.put("mail.smtp.auth.plain.disable", "true");

session = Session.getInstance(prop);
session.setDebug(true);
String accessToken = getOAuth2AccessToken();

transport = session.getTransport("smtp");
transport.connect(emailSettings.getSmtp().getHostname(), emailSettings.getSmtp().getPort(), emailSettings.getSmtp().getUsername(), tokenForSmtp(emailSettings.getSmtp().getUsername(), accessToken));
/* -- */ 
transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
transport.close();

method for calling/getting token from Azure AD, in response we get a token with expiration time.

String url = "https://login.microsoftonline.com/" + Tenant_ID + "/oauth2/token";

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "client_credentials");
map.add("response_type", "code");
map.add("client_id", ClientId);
map.add("client_secret", ClientSecret);
map.add("scope","openid offline_access https%3A%2F%2Foutlook.office365.com%2FSMTP.Send ");

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<AzureResponse> response = restTemplate.postForEntity(url, map, AzureResponse.class);

method for preparing token for sending within the SMTP send-mail process

private String tokenForSmtp(String userName, String accessToken) {
    final String ctrlA=Character.toString((char) 1);
    final String coded= "user=" + userName + ctrlA+"auth=Bearer " + accessToken + ctrlA+ctrlA;
    return Base64.getEncoder().encodeToString(coded.getBytes());
    //base64("user=" + userName + "^Aauth=Bearer " + accessToken + "^A^A")
}

After send of SMTP email I receive error:

AUTH XOAUTH2 dXNlcj1zb2ZhQHNvbHV0aW9uZmFjdG9yeWFnLm9ub...=
535 5.7.3 Authentication unsuccessful [VI1PR0202CA0024.eurprd02.prod.outlook.com]
Error on sending email: 535 5.7.3 Authentication unsuccessful [VI1PR0202CA0024.eurprd02.prod.outlook.com]

Do I need a some other token scope's from Azure AD other then openid, offline_access and https://outlook.office.com/SMTP.Send ? Or do I miss something else in the configuration of Azure? Of someone has a Java example of how it should be done, please feel free to attach it. Also if you have some screenshots of what to set up on the Azure account.

Upvotes: 7

Views: 4843

Answers (1)

Wisdom Jere
Wisdom Jere

Reputation: 86

I managed to do this using a combination of Azure.Identity and Microsoft.Graph SDKs. I will try to give you a step-by-step procedure.

  1. Create App Registration in Azure Portal

    You can follow the documentation from microsoft learn. Make sure the app is granted the necessary permissions you can do in Azure Portalthat via [Microsft Entra ID]-> [App Registrations] -> [Click the app you registered]-> [Manage]-> [API permissions] -> [Add new] Search for Microsoft Graph -> Mail.Send (As any user).

  2. Add Necessary Packages to Your Java Application

dependencies{
   implementation 'com.microsoft.graph:microsoft-graph:6.27.0'
   implementation 'com.azure:azure-identity:1.15.0'
}
  1. Configurations
azure:
  client-id: 'Your Client Id'
  client-secret: 'Client Secret'
  tenant-id: 'Ternant Id'
  scope: 'https://graph.microsoft.com/.default'
@Configuration
@ConfigurationProperties(prefix = "azure")
@Data
public class AzureConfig {
    private String clientId;
    private String clientSecret;
    private String tenantId;
    private String scope;

    @Bean
    public ClientSecretCredential clientSecretCredential() {
        return new ClientSecretCredentialBuilder()
                .clientId(clientId)
                .clientSecret(clientSecret)
                .tenantId(tenantId)
                .build();
    }

    @Bean
    public GraphServiceClient graphServiceClient() {
        return new GraphServiceClient(clientSecretCredential(), scope);
    }
}

  1. Email Service Implementation

@Service
public class SimpleMailSender {
    @Autowired
    private graphServiceClient graphServiceClient;
    private final String mailFrom = "[email protected]";

    public void sendMail(String recipient, String subject, String body) {
        try {
            // Create the email message
            Message message = new Message();
            message.setSubject(subject);

            ItemBody mailBody = new ItemBody();
            mailBody.setContentType(BodyType.Text);// or BodyType.Html
            mailBody.setContent(body);
            message.setBody(mailBody);

            // To recipient
            Recipient toRecipient = new Recipient();
            EmailAddress toAddress = new EmailAddress();
            toAddress.setAddress(recipient);
            toRecipient.setEmailAddress(toAddress);
            message.setToRecipients(List.of(toRecipient));

            SendMailPostRequestBody requestBody = new SendMailPostRequestBody();
            requestBody.setMessage(message);
            requestBody.setSaveToSentItems(false);

            graphServiceClient.users()
                .byUserId(mailFrom)
                .sendMail()
                .post(requestBody);
        } catch (Exception e) {            
            throw new RuntimeException(e);
        }
    }
}

Hope that helps!!

Upvotes: 1

Related Questions