How to access outlook.office365.com IMAP form Java with OAUTH2

Since Microsoft has announced that access to Outlook IMAP mailboxes with Basic authentication will soon no longer be possible, it is time to change many ‘older’ Java implementations. The following code example shows how to access outlook.office365.com with OAuth2 :

Maven Dependency

First you need to enjure that you are using a Java MailAPI version 1.6.2 or higher. I also use Apache httpclient and fasterxml.jackson library here to post the access credentials and extract the JSON token. So I added the following Maven dependencies to my project:

    <!-- Microsoft oauth token -->
    <dependency>
        <groupId>com.sun.mail</groupId>
        <artifactId>javax.mail</artifactId>
        <version>1.6.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.10</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.3</version>
    </dependency>

Get the OAuht2 Access Token

Next you need to implement a method to request a OAuth2 Access Token via http form https://login.microsoftonline.com/

public String getAuthToken(String tanantId,String clientId,String client_secret) throws ClientProtocolException, IOException {
    CloseableHttpClient client = HttpClients.createDefault();
    HttpPost loginPost = new HttpPost("https://login.microsoftonline.com/" + tanantId + "/oauth2/v2.0/token");
    String scopes = "https://outlook.office365.com/.default";
    String encodedBody = "client_id=" + clientId + "&scope=" + scopes + "&client_secret=" + client_secret
            + "&grant_type=client_credentials";
    loginPost.setEntity(new StringEntity(encodedBody, ContentType.APPLICATION_FORM_URLENCODED));
    loginPost.addHeader(new BasicHeader("cache-control", "no-cache"));
    CloseableHttpResponse loginResponse = client.execute(loginPost);
    InputStream inputStream = loginResponse.getEntity().getContent();
    byte[] response = readAllBytes(inputStream);
    ObjectMapper objectMapper = new ObjectMapper();
    JavaType type = objectMapper.constructType(
            objectMapper.getTypeFactory().constructParametricType(Map.class, String.class, String.class));
    Map<String, String> parsed = new ObjectMapper().readValue(response, type);
    return parsed.get("access_token");
}

The generated token can be checked with jwt.ms .

With this token you can now create and open a Java Mail connection as usual:

        Properties props = new Properties();

        props.put("mail.store.protocol", "imap");
        props.put("mail.imap.host", "outlook.office365.com");
        props.put("mail.imap.port", "993");
        props.put("mail.imap.ssl.enable", "true");
        props.put("mail.imap.starttls.enable", "true");
        props.put("mail.imap.auth", "true");
        props.put("mail.imap.auth.mechanisms", "XOAUTH2");
        props.put("mail.imap.user", mailAddress);
        props.put("mail.debug", "true");
        props.put("mail.debug.auth", "true");
    
        // open mailbox....
        String token = getAuthToken(tanantId,clientId,client_secret);
        Session session = Session.getInstance(props);
        session.setDebug(true);
        Store store = session.getStore("imap");
        store.connect("outlook.office365.com", mailAddress, token);

The output will look like this one (including debug messages):

DEBUG: setDebug: JavaMail version 1.6.2
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: enable SASL
DEBUG IMAPS: SASL mechanisms allowed: XOAUTH2
DEBUG IMAPS: closeFoldersOnStoreFailure
OAUTH2 IMAP trying to connect with system properties to Host:outlook.office365.com, Port: 993, userEmailId: test@foo.com, AccessToken: ey........_w
DEBUG IMAPS: trying to connect to host "outlook.office365.com", port 993, isSSL true
* OK The Microsoft Exchange IMAP4 service is ready. [QQ.......AA==]
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS MOVE 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=test@foo.com, password=<non-null>
DEBUG IMAPS: SASL Mechanisms:
DEBUG IMAPS:  XOAUTH2
DEBUG IMAPS: 
DEBUG IMAPS: SASL client XOAUTH2
DEBUG IMAPS: SASL callback length: 2
DEBUG IMAPS: SASL callback 0: javax.security.auth.callback.NameCallback@2d2ffcb7
DEBUG IMAPS: SASL callback 1: javax.security.auth.callback.PasswordCallback@762ef0ea
A1 AUTHENTICATE XOAUTH2 ....E=
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.

That’s it!

A1 NO AUTHENTICATE failed

In case you get the following exception:

A1 NO AUTHENTICATE failed.
javax.mail.AuthenticationFailedException: AUTHENTICATE failed.

this indicates that the servicePrincipal has insufficient access rights. This is an issue that can only be solved by the Administrator of the office365 account running a PowerShell script on the Exchange. See also discussion here.

22 Replies to “How to access outlook.office365.com IMAP form Java with OAUTH2”

  1. Permanent error A1 NO AUTHENTICATE failed. Can you tell me what scripts? What should they change?

    If I upgrade jakarta.mail to 2.0.1 this error goes away. But there is a new: javax.mail.NoSuchProviderException: imaps

    All together just does not work, unfortunately.

  2. The clientid/secret is something your MS Admin needs to provide to you. It seems to be a hell of an admin interface. There are many discussions about how to administrate the Azure Active Directory…. (I wonder why so many companies use Microsoft Outlook as a mail solution. There are so many simple Open Soruce solutions.)

  3. @Kiran – you should not store such a token in a long running session or in a application wide scope. You should create the token on demand only each time you whant to access the mailbox.

  4. Even if I didn`t you your code, this helped me a lot to validate mine 🙂
    Thank you for the post!

  5. Hi @Ralph I tried the same method as you mentioned above but I am sill getting javax.mail.AuthenticationFailedException: AUTHENTICATE failed.

    We have checked Azure AD & office 365 setting and it’s configured correctly.
    As you mentioned gave all access rights to servicePrincipal.

    But still we are getting the same issue. What we will do then

  6. Hi @Rami, it is usually a problem with the authroisation of the service account. This somethong about the account in AD. I am sorry, I am not the Microsoft expert and can’t explain you how to setup the service account correctly.

  7. Thanks @Ralph for the quick response.

    Could you please help us with the correct AD configuration settings, permission scope needed for a single tenant java(spring) application which need to read an email from outlook inbox.

  8. I am getting error in this line –
    JavaType type = objectMapper.constructType(
    objectMapper.getTypeFactory().constructParametricType(Map.class, String.class, String.class));

    java.lang.ClassCastException: org.codehaus.jackson.map.type.MapType cannot be cast to java.lang.reflect.Type

    Anyone please helps me to solve thus

  9. byte[] response = readAllBytes(inputStream); //
    The method readAllBytes(InputStream) is undefined
    I am using java 8. Is there any methods available for read that?

  10. Hi @Ralph,

    I debug the code and i m able to see the correct error when we are trying to call
    store.connect(host, userEmailId, oauth2AccessToken);

    I am getting below error. can you please help me on this
    javax.mail.MessagingException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate); nested exception is: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate) at com.sun.mail.imap.IMAPStore.protocolConnect(IMAPStore.java:670) at javax.mail.Service.connect(Service.java:295) at javax.mail.Service.connect(Service.java:176)

    I used the same properties as you mentioned in the blog

  11. Hi @Ralph I noticed one more thing during debug, can you check the same

    DEBUG: getProvider() returning javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Oracle]

    In your DEBUG, it is imaps,com.sun.mail.imap.IMAPSSLStore – IMAPSSLStore I used the same property as you mentioned, So will it impact?

    Attaching the debug log:
    DEBUG: setDebug: JavaMail version 1.6.2
    DEBUG: successfully loaded resource: /META-INF/javamail.default.providers
    DEBUG: Tables of loaded providers from javamail.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: getProvider() returning javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Oracle]
    DEBUG IMAP: mail.imap.fetchsize: 16384
    DEBUG IMAP: mail.imap.ignorebodystructuresize: false
    DEBUG IMAP: mail.imap.statuscachetimeout: 1000
    DEBUG IMAP: mail.imap.appendbuffersize: -1
    DEBUG IMAP: mail.imap.minidletime: 10
    DEBUG IMAP: enable STARTTLS
    DEBUG IMAP: trying to connect to host “outlook.office365.com”, port 993, isSSL true
    javax.mail.MessagingException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate);
    nested exception is:
    javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

  12. Sorry to write again, I changed below property and now I am getting

    props.put(“mail.store.protocol”, “imaps”);

    DEBUG: getProvider() returning javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle] – same as yours.

    Still getting below issue.
    DEBUG IMAPS: trying to connect to host “outlook.office365.com”, port 993, isSSL true
    javax.mail.MessagingException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate);
    nested exception is:
    javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

    It would be great if you can give me any lead on the same

  13. Hello @Ralph,
    I was getting the error:
    A1 NO AUTHENTICATE failed.

    After spending sometime on the Java mail open source code and studying their properties consumed, I was able to run this Code with some minor changes,

    These are the changes I did that worked for me:
    1. There was little tweak introduced in the properties:
    a. Add props.put(“mail.imap.sasl.enable”, “true”);
    b. Add props.put(“mail.imap.sasl.mechanisms”, “XOAUTH2”);

    I was able to fetch the Inbox –> Message Count

    Cheers…..

  14. We are using certificates for authentication not client secret. Is there a way to use getAuthToken with certificates instead of client secret?

  15. Hey guys, I had several problems, trying to authenticate and get acess to the mail box mails. The changes made by me:

    First:

    public static String getAuthToken(String tanantId,String clientId,String client_secret) throws ClientProtocolException, IOException {
    CloseableHttpClient client = HttpClients.createDefault();

    HttpPost loginPost = new HttpPost(“https://login.microsoftonline.com/” + tanantId + “/oauth2/v2.0/token”);

    String scopes = “https://outlook.office365.com/IMAP.AccessAsUser.All”;
    String encodedBody =
    “client_id=” + clientId +
    “&scope=” + scopes +
    “&username=” + System.getenv(“EMAIL_USER”) +
    “&password=” + System.getenv(“EMAIL_PASSWORD”) +
    “&client_secret=” + client_secret +
    “&grant_type=password”;

    loginPost.setEntity(new StringEntity(encodedBody, ContentType.APPLICATION_FORM_URLENCODED));
    loginPost.addHeader(new BasicHeader(“cache-control”, “no-cache”));
    CloseableHttpResponse loginResponse = client.execute(loginPost);
    InputStream inputStream = loginResponse.getEntity().getContent();

    byte[] response = inputStream.readAllBytes();
    ObjectMapper objectMapper = new ObjectMapper();
    JavaType type = objectMapper.constructType(
    objectMapper.getTypeFactory().constructParametricType(Map.class, String.class, String.class));
    Map parsed = new ObjectMapper().readValue(response, type);
    return parsed.get(“access_token”);
    }

    Here it’s very important, requesting a token with e-mail and password of the mail box target, to do this, add:

    “&password=” + System.getenv(“EMAIL_PASSWORD”) +

    Change:
    “&grant_type=password”;

    And:
    String scopes = “https://outlook.office365.com/IMAP.AccessAsUser.All”;
    /\
    Here are, the necessary scopes to read the mail box using IMAP.

    Now, the generated token, must be compatible to access the mail box.

    Second:

    Remove the properies:
    props.put(“mail.imap.auth”, “true”);
    props.put(“mail.imap.auth.mechanisms”, “XOAUTH2”);

    And add:
    props.put(“mail.imap.sasl.enable”, “true”);
    props.put(“mail.imap.sasl.mechanisms”, “XOAUTH2”);

    If you are using the IMAPS protocol replace “imap” for “imaps”
    exemple:
    Properties props = new Properties();
    props.put(“mail.store.protocol”, “imaps”);
    props.put(“mail.imaps.host”, “outlook.office365.com”);
    props.put(“mail.imaps.port”, “993”);
    props.put(“mail.imaps.ssl.enable”, “true”);
    props.put(“mail.imaps.starttls.enable”, “true”);
    props.put(“mail.imaps.sasl.enable”, “true”);
    props.put(“mail.imaps.sasl.mechanisms”, “XOAUTH2”);
    props.put(“mail.imaps.user”, System.getenv(“EMAIL_USER”));
    props.put(“mail.debug”, “true”);
    props.put(“mail.debug.auth”, “true”);

    To work correctly is very very very IMPORTANT, to read this tutorial:
    https://learn.microsoft.com/pt-br/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth

    It will teach how configure the azure’s pemissions.

    I hope helped you! Good luck guys!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.