[JAVA] Receive emails with OAuth 2.0 authentication in Exchange Online

I was able to receive an email from the article Last time, but in the end I didn't see the light of day in my work. About a year later, there was talk of developing an app to receive new emails, and when I was assured that it would be an easy win, the world would be like this. .. ..

If you need to remodel in less than a year, it is humanity that you want to deal with from the beginning. I heard that it is necessary to play with the Exchange Online side if I check in advance, so I can not use the current operating environment, I applied for free Exchange Online for one month, and it is an advanced authentication based on OAuth 2.0 I tried to receive.

By the way, the application for development is a resident application that periodically outputs the mail received by the mail account dedicated to the application to a text file, and as an authentication method, [ROPC flow recommended not to use](https:: //docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-oauth-ropc) is assumed, so the following is an implementation example.

Application registration

I referred to the Tutorial for the general processing flow and this procedure.

  1. Log in to the Azure Active Directory admin center with the account that the app uses to receive emails, and click [Azure Active Directory]-[App Registration]-[New Registration]. image.png

  2. Enter the name of the app. This time, the app is used only for a specific account, so select [Accounts included only in this organization directory]. I don't use the redirect URI, so don't touch it. Then click [Register]. image.png

  3. Set [Allow public client flow] to [Yes] and click [Save]. image.png

  4. In [Add Permission] of [API Permission], add [IMAP.AccessAsUser.All] from [Delegated Permission] of [Microsoft Graph]. This time, the logged-in account is also the administrator, so just click [Give administrator consent to ~]. image.png

Creating the underlying application

I used Spring Boot 2.3.4 and Kotlin for simplification. In the dependency of spring initializer, add only Java Mail Sender (that is, spring-boot-starter-mail) and add Microsoft Authentication Library for Java to pom.xml after creating the project.

pom.xml


<dependency>
  <groupId>com.microsoft.azure</groupId>
  <artifactId>msal4j</artifactId>
  <version>1.7.1</version>
</dependency>

Implement CommandLineRunner and write the process in the run method.

@SpringBootApplication
class SampleMailApplication : CommandLineRunner {
  override fun run(vararg args: String?) {
    //Implementation
  }
}

Get access token

This procedure of the tutorial referenced above is not cool. What's the rest of the C # article, "Open App.xaml"? .. .. I had no choice but to look it up on the net and it started working with the following code.

import com.microsoft.aad.msal4j.PublicClientApplication
import com.microsoft.aad.msal4j.UserNamePasswordParameters

//Omitted
override fun run(vararg args: String?) {
  //Specify the application ID on the overview page of the registered application
  val applicationId = "..."
  // ...Specify the directory ID on the overview page in the part
  val authEndpoint = "https://login.microsoftonline.com/.../oauth2/v2.0/authorize"
  //Specify according to the protocol to be used
  val scope = setOf("https://outlook.office365.com/IMAP.AccessAsUser.All",
    "https://outlook.office365.com/SMTP.Send")
  //Specify the email address of your account
  val username = "..."
  //Specify the password for the account
  val password = "..."

  val pca = PublicClientApplication.builder(applicationId)
    .authority(authEndpoint)
    .build()

  val parameters = UserNamePasswordParameters
    .builder(scope, username, password.toCharArray())
    .build()
  val result = pca.acquireToken(parameters).join()
  println("Access token: ${result.accessToken()}")

incoming mail

var props = Properties()
//OAuth 2 for authentication.Use 0
props["mail.imaps.auth.mechanisms"] = "XOAUTH2"
var session: Session = Session.getInstance(props)
val store: Store = session.getStore("imaps")
//Use access token for password
store.connect("outlook.office365.com", 993, username, result.accessToken())
val folderInbox: Folder = store.getFolder("INBOX")
folderInbox.open(Folder.READ_ONLY)
folderInbox.messages.forEach { println("subject: ${it.subject}") }

The first point is mail.imaps.auth.mechanisms [Description](https://github.com/eclipse-ee4j/mail/blob/c3096cbeaf566f36998e96ff384378059f291ce8/mail/src/main/java/com/sun/mail/imap According to /package.html#L405-L410), the default value is all supported authentication except XOAUTH2, and Exchange Online uses basic authentication. Therefore, specify XOAUTH2 to enable OAuth 2.0 authentication.

Then, just specify the access token instead of the password, and you can receive emails with OAuth 2.0 authentication.

(Supplement) Send email

For the time being, I would like to try OAuth 2.0 authentication for SMTP basic authentication, which will be abolished soon. However, instead of OAuth 2.0 authentication, even basic authentication

javax.mail.AuthenticationFailedException: 535 5.7.3 Authentication unsuccessful

Isn't it![Authenticated SMTP on mailbox is on](https://docs.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission#use-the -microsoft-365-admin-center-to-enable-or-disable-smtp-auth-on-specific-mailboxes). .. ..

The page says, "If the authentication policy disables Basic SMTP Authentication, the client will not be able to use the SMTP Authentication Protocol even if you enable the settings described in this article." If you set [Enable Defaults] to [No]](https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/concept-fundamentals-security-defaults), the mail will be sent successfully. However, when I set [Enable Security Defaults] back to [Yes], the error no longer occurs. (It is undeniable that something else has been tampered with ...)

I don't know what it is ...

For the time being, the successful transmission logic is also described.

val props = Properties()
props["mail.smtp.auth"] = "true"
props["mail.smtp.auth.mechanisms"] = "XOAUTH2";
props["mail.smtp.starttls.enable"] = "true"
val session = Session.getInstance(props)
val transport = session.getTransport("smtp")
transport.connect("smtp.office365.com", 587, username, result.accessToken())

val sendMessage = MimeMessage(session)
sendMessage.addRecipients(Message.RecipientType.TO, username)
sendMessage.setFrom(username)
sendMessage.subject = "Send"
sendMessage.setText("Transmission test")

transport.sendMessage(sendMessage, sendMessage.allRecipients)

In addition, although the transmission itself has been completed, the final delivery has not been completed with "550 5.7.501 Service unavailable. Spam abuse detected from IP range." From the destination. This is because emails from the onmicrosoft.com domain are filtered as spam, and a trial send from Outlook will give the same result, so I don't think it's a logical issue.

Recommended Posts

Receive emails with OAuth 2.0 authentication in Exchange Online
Oauth2 authentication with Spring Cloud Gateway
SSO with GitHub OAuth in Spring Boot 1.5.x environment