DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Enterprise AI Trend Report: Gain insights on ethical AI, MLOps, generative AI, large language models, and much more.

2024 Cloud survey: Share your insights on microservices, containers, K8s, CI/CD, and DevOps (+ enter a $750 raffle!) for our Trend Reports.

PostgreSQL: Learn about the open-source RDBMS' advanced capabilities, core components, common commands and functions, and general DBA tasks.

AI Automation Essentials. Check out the latest Refcard on all things AI automation, including model training, data security, and more.

Related

  • Spring OAuth Server: Token Claim Customization
  • How To Implement OAuth User Authentication in Next.js
  • Enhancing Security with Two-Factor Authentication: An Introduction to TOTP and HOTP
  • Modern Digital Authentication Protocols

Trending

  • Do We Need Data Normalization Anymore?
  • Vector Tutorial: Conducting Similarity Search in Enterprise Data
  • How To Get Started With New Pattern Matching in Java 21
  • How to Submit a Post to DZone
  1. DZone
  2. Data Engineering
  3. Data
  4. IMAP OAuth 2.0 Authorization in Exchange Online

IMAP OAuth 2.0 Authorization in Exchange Online

This article shows how a Java-based client application can connect to an e-mail server via IMAP protocol after obtaining an OAuth 2.0 access token.

By 
Horatiu Dan user avatar
Horatiu Dan
·
Nov. 23, 22 · Tutorial
Like (1)
Save
Tweet
Share
7.8K Views

Join the DZone community and get the full member experience.

Join For Free

Microsoft announced that starting October 2022, Basic authentication for specific protocols in Exchange Online would be considered deprecated and turned off gradually and randomly for certain tenants. As insightful details concerning this topic may be found in Resources items one and two, among these protocols, there are Exchange ActiveSync (EAS), POP, IMAP, Remote PowerShell, Exchange Web Services (EWS), Offline Address Book (OAB).

Consequently, customer applications leveraging Basic authentication towards Exchange Online as part of their business use cases need to replace it with Modern authentication —  OAuth 2.0 token-based authorization —  which no doubt has many benefits and improvements that help mitigate the former's risks.

The purpose of this article is to document and showcase how a Java-based client application can connect to an e-mail server via IMAP (or IMAPS) protocol using the JavaMail library after previously obtaining an OAuth 2.0 access token. The token retrieval is implemented in two different manners - by leveraging the OAuth 2.0 Resource Owner Password Credentials (ROPC) grant and using Microsoft Authentication Library (MSAL) for Java —  thus letting developers choose the preferred way.

Assumptions

Prior to successfully running the sample code, the Exchange server set-up shall be fulfilled to require an OAuth 2.0 authentication mechanism. Details on how a system administrator may configure it can be found at least in Resources item seven. Moreover, significant gotchas described in Resources item eight proved to be very helpful. Briefly, when creating the service principal using PowerShell in Exchange Online, one shall use the ObjectId of the enterprise application as the ServiceId, as the application ObjectId is different from the enterprise application ObjectId.

Set-up

The proof of concept uses the following:

  • Java 17
  • Maven 3.6.3
  • Spring Boot version 2.7.5
  • JavaMail version 1.6.2
  • MSAL4J version 1.13.2

Connecting via JavaMail

According to Oracle (Resources item 3), starting with version 1.5.2, JavaMail supports OAuth 2 authentication via the SASL XOAUTH2 mechanism. While IMAP and SMTP protocols are covered, POP3 is not. Nevertheless, the proof of concept in this article uses IMAP and demonstrates a simple use case —  a session is created, then used to connect to a store to get a folder with a specified name.

Since the important aspect here is connecting via IMAP and authenticating using OAuth 2, a great deal of other use cases may be easily implemented as needed.

In order to accomplish the aimed scenario, a simple MailReader component is created.

Java
 
public class MailReader {

    private final MailProperties mailProperties;
    private final TokenProvider tokenProvider;

    public MailReader(MailProperties mailProperties, TokenProvider tokenProvider) {
        this.mailProperties = mailProperties;
        this.tokenProvider = tokenProvider;
    }

    public Folder getFolder(String name) {
        final Properties props = new Properties();
        props.put("mail.debug", "true");
        props.put("mail.store.protocol", "imaps");
        props.put("mail.imaps.port", 993);
        props.put("mail.imaps.ssl.enable", "true");
        props.put("mail.imaps.starttls.enable", "true");
        props.put("mail.imaps.auth.mechanisms", "XOAUTH2");

        Session session = Session.getInstance(props);
        try {
            Store store = session.getStore();            
            store.connect(mailProperties.getHost(), mailProperties.getUser(), tokenProvider.getAccessToken());
            return store.getFolder(name);
        } catch (MessagingException e) {
            throw new RuntimeException("Unable to connect to the default folder.", e);
        }
    }
}


getFolder() function sets up the connection properties, connects, and returns the folder.

A brief comparison between Basic and OAuth 2.0 authentication methods is worth doing at this point. In order to connect, a client application uses an account set-up accordingly. Concerning the implementation, the differences between the two methods are minimal. For OAuth 2.0:

  • mail.imaps.auth.mechanisms property shall be set to XOAUTH2.
  • The password parameter of the Store#connect() method contains an access token instead of the configured password.

Retrieving an Access Token

For connecting to the store via IMAP(S) using OAuth 2.0 authentication, one shall first obtain an access token. In either of the two ways —  ROPC grant or MSAL —  the client application needs the following pieces of information whatsoever:

  • The authentication URL.
  • The tenant identifier —  the tenant directory the user is logged into.
  • The client identifier —  the Application (client) ID, the portal page assigned to the application.
  • The client's secret, required if the application is a confidential client.

The two ways of retrieving the token are described in the next sections. In this direction, this proof of concept defines the following interface.

Java
 
public interface TokenProvider {

    String getAccessToken();
}

And then, for each of them, an implementation is coded.

Using Resource Owner Password Credentials (ROPC) Grant

The ROPC flow is pretty straightforward; it requires a single HTTP POST call toward the authorization endpoint that corresponds to the particular tenant. The request shall contain the client identifier, the client secret, the scope (usually https://outlook.office365.com/.default), and the 'client_credentials' or 'password' grant type. The response contains the access token necessary to connect afterward.

The implementation uses a WebClient instance to perform the POST call.

Java
 
public class RopcTokenProvider implements TokenProvider {

    private final MailProperties mailProperties;
    private final WebClient client;

    public RopcTokenProvider(MailProperties mailProperties) {
        this.mailProperties = mailProperties;

        client = WebClient.builder()
                .baseUrl(mailProperties.getAuthUrl())
                .build();
    }

    @Override
    public String getAccessToken() {
        MultiValueMap<String, String> bodyValues = new LinkedMultiValueMap<>();
        bodyValues.add("client_id", mailProperties.getClientId());
        bodyValues.add("client_secret", mailProperties.getClientSecret());
        bodyValues.add("scope", "https://outlook.office365.com/.default");
        bodyValues.add("grant_type", "client_credentials");

        TokenDTO token = client.post()
                .uri("/{tenantId}/oauth2/v2.0/token", mailProperties.getTenantId())
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .accept(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromFormData(bodyValues))
                .retrieve()
                .bodyToMono(TokenDTO.class)
                .block();

        if (token == null) {
            throw new RuntimeException("Unable to retrieve OAuth2 access token.");
        }
        return token.getAccessToken();
    }
}


Two useful aspects are worth observing here:

  • the endpoint URL is https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
  • request content type is x-www-form-urlencoded

A successful HTTP POST result has the following form:

JSON
 
{
    "token_type": "Bearer",
    "expires_in": 3599,
    "ext_expires_in": 3599,
    "access_token": "the access token"
}


And maybe unmarshalled into an object as the TokenDTO below.

Java
 
@Data
public class TokenDTO {

    @JsonProperty("token_type")
    private String tokenType;

    @JsonProperty("access_token")
    private String accessToken;

    @JsonProperty("expires_in")
    private int expiresIn;

    @JsonProperty("ext_expires_in")
    private int extExpiresIn;
}


The important piece here is obviously the accessToken.

Using Microsoft Authentication Library (MSAL)

In order to retrieve an access token using MSAL for Java, the client application classpath shall be enhanced with the following library:

XML
 
<dependency>
	<groupId>com.sun.mail</groupId>
	<artifactId>javax.mail</artifactId>
	<version>1.6.2</version>
</dependency>


In this case, getAccessToken() method of the TokenProvider implementation returns directly the token used to connect to the store.

Java
 
public class MsalTokenProvider implements TokenProvider {

    private final ConfidentialClientApplication confidentialClientApp;
    private final Set<String> scopes;

    public MsalTokenProvider(MailProperties mailProperties) throws MalformedURLException {
        IClientCredential credential = ClientCredentialFactory.createFromSecret(mailProperties.getClientSecret());

        final String authority = String.format("%s/%s",
                mailProperties.getAuthUrl(), mailProperties.getTenantId());
        confidentialClientApp = ConfidentialClientApplication.builder(mailProperties.getClientId(), credential)
                .authority(authority)
                .build();

        scopes = Set.of("https://outlook.office365.com/.default");
    }

    @Override
    public String getAccessToken() {
        try {
            ClientCredentialParameters parameters = ClientCredentialParameters
                    .builder(scopes)
                    .build();

            return confidentialClientApp.acquireToken(parameters)
                    .join()
                    .accessToken();
        } catch (Exception e) {
            throw new RuntimeException("Unable to retrieve OAuth2 access token.", e);
        }
    }
}


Testing the Solutions

The pieces of information needed to authenticate that have been previously mentioned are set as application properties. Except for mail.host and mail.authUrl ones, the values shall be filled in with the actual values configured on the Exchange server.

Properties files
 
mail.host = outlook.office365.com
mail.user = technically@correct.com

mail.authUrl = https://login.microsoftonline.com
mail.tenantId = tenantid
mail.clientId = clientid
mail.clientSecret = secret


If either of the following integration tests is run, details may be observed while connecting to the store. As mail.debug session property was set to true; the logs will depict these.

Java
 
@SpringBootTest
class MailReaderTest {

    @Autowired
    @Qualifier("msalMailReader")
    private MailReader msalMailReader;

    @Autowired
    @Qualifier("ropcMailReader")
    private MailReader ropcMailReader;

    private void getFolder(MailReader mailReader) {
        final String name = "INBOX";
        Folder folder = mailReader.getFolder("INBOX");
        Assertions.assertNotNull(folder);
        Assertions.assertEquals(name, folder.getFullName());
    }

    @Test
    void getFolderMsal() {
        getFolder(msalMailReader);
    }

    @Test
    void getFolderRopc() {
        getFolder(ropcMailReader);
    }
}


What happens when connecting?

Plain Text
 
DEBUG: JavaMail version 1.6.2
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
DEBUG: getProvider() returning javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle]
DEBUG IMAPS: mail.imap.fetchsize: 16384
DEBUG IMAPS: mail.imap.ignorebodystructuresize: false
DEBUG IMAPS: mail.imap.statuscachetimeout: 1000
DEBUG IMAPS: mail.imap.appendbuffersize: -1
DEBUG IMAPS: mail.imap.minidletime: 10
DEBUG IMAPS: enable STARTTLS
DEBUG IMAPS: closeFoldersOnStoreFailure
DEBUG IMAPS: trying to connect to host "outlook.office365.com", port 993, isSSL true
* OK The Microsoft Exchange IMAP4 service is ready. [TQBOADIAUABSADEANgBDAEEAMAAwADUANgAuAG4AYQBtAHAAcgBkADEANgAuAHAAcgBvAGQALgBvAHUAdABsAG8AbwBrAC4AYwBvAG0A]
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+
A0 OK CAPABILITY completed.
DEBUG IMAPS: AUTH: PLAIN
DEBUG IMAPS: AUTH: XOAUTH2
DEBUG IMAPS: protocolConnect login, host=outlook.office365.com, user=technically@correct.com, password=<non-null>
DEBUG IMAPS: AUTHENTICATE XOAUTH2 command trace suppressed
DEBUG IMAPS: AUTHENTICATE XOAUTH2 command result: A1 OK AUTHENTICATE completed.
A2 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS MOVE ID UNSELECT CLIENTACCESSRULES CLIENTNETWORKPRESENCELOCATION BACKENDAUTHENTICATE CHILDREN IDLE NAMESPACE LITERAL+
A2 OK CAPABILITY completed.
DEBUG IMAPS: AUTH: PLAIN
DEBUG IMAPS: AUTH: XOAUTH2


The OAuth 2.0 authentication is successful.

Conclusions

This article documents possible implementations that client applications shall accomplish in order to embrace the Modern authentication (OAuth 2.0) promoted by Microsoft and thus remain functional after Basic Authentication will have been turned off. While the connection via the JavaMail library does not imply major changes, the access token retrieval needs to be coded. Two different mechanisms were implemented, either by leveraging the Resource Owner Password Credentials grant or using Microsoft Authentication Library for Java.

The former is apparently more lightweight, as it is done via an HTTP POST call to the Authorization server. According to Microsoft, more secure alternatives are available and recommended (Resources item five).

The latter needs to include the msal4j library as part of the client application classpath; nevertheless, taking into account the recommendations, it seems the preferred manner of retrieving an access token.

Component Object Model authentication security Data Types

Published at DZone with permission of Horatiu Dan. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Spring OAuth Server: Token Claim Customization
  • How To Implement OAuth User Authentication in Next.js
  • Enhancing Security with Two-Factor Authentication: An Introduction to TOTP and HOTP
  • Modern Digital Authentication Protocols

Partner Resources


Comments

ABOUT US

  • About DZone
  • Send feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: