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.
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.
where or how did you get the clientId / secret ?
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.)
It works perfectly, thank you very much!
Hi @Fabio
Can you post your complete code here once
What if the token is expired? how to reconnect using spring boot ?
@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.
Even if I didn`t you your code, this helped me a lot to validate mine 🙂
Thank you for the post!
* didn`t use 🙂
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
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.
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.
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
The important part is to extract the token form the json result.
Your error message indicates that you have a mismatch in your Json lib.
If you are using jakarta EE you can find here a more compact implementation how to get the message store object:
https://github.com/imixs/imixs-archive/blob/master/imixs-archive-importer/src/main/java/org/imixs/archive/importer/mail/IMAPOutlookAuthenticator.java
Thank you Ralph Soika
byte[] response = readAllBytes(inputStream); //
The method readAllBytes(InputStream) is undefined
I am using java 8. Is there any methods available for read that?
you can simply use InputStream directly instead of response
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
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)
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
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…..
We are using certificates for authentication not client secret. Is there a way to use getAuthToken with certificates instead of client secret?
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!
This has helped me a lot. I’m finally able to read email from a Microsoft 365 account using a ROPC flow. much appreciated
Hi Im new to Java …. this program is very important to me … can anyone kindly share me the GITHUB working link..
Hi I have the follow error: javax.mail.MessagingException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target;
this is with JDK 1.8, I need to install microsoft certificate to cacerts to run ?
Jan, sometimes that’s indeed the solution (and some will say you must), but often, all you need to do is update your java–and I mean your java 8. It may be that the server your calling or the cert it uses is of a newer capability than is supported by your current Java version.
The latest from Oracle is 1.8.0_361, released last month. Yours may be much older than that. Then again, if you use Oracle’s Java for production and have not licensed it, the last free update was 1.8.0_202 in Apr 2019. Even so, again yours may be still older, and updating even to that may solve your issue.
Or you could consider an openjdk, or even Java 17 (now free again for production, and the current “LTS” version. )
Of course, there can be important security reasons to be on the latest available update. But then again your app could have a problem with even updating WITHIN your current version, let alone updating to another (like 8 to 17, or later).
Indeed, changing jvms in any way is always something to be careful about. And some installers will remove older versions, so be sure to have on hand the installer for your current version in case you need to revert back to it and find it’s been removed.
And sometimes problems like yours are solved by adding jvm arguments/system properties that help configure Java to better work with that server or cert you’re calling.
Again, maybe you need only “import a cert” as you proposed initially, but if not, I hope the rest here may help.
Finally, another thing about importing certs is that if and when you MAY update your jvm, it’s on YOU to re-import any needed PREVIOUS certs you’d imported…though sometimes you DO NOT need them any more. It may be that they were imported specifically BECAUSE your jvm was old, and your root certs were old, etc.
All this can be messy and a lot to juggle, but if the above is understood it can all go well. I’ve done or helped people do such jvm updates, jvm args, and cert manipulation for a couple decades, indeed this week. I have far more in blog posts on updating such jvm issues at carehart.org/blog. See the Java category.
does it work with POP3 ?
Hi Thank you for providing this guide, it helped me in implementing modern authentication.
I am getting an error “Client not authenticated to send email”.
These are the two permissions admin has set in the app i registered in azure.
Microsoft Graph -> User read
Office 365 Exchange online->SMTP.SendAsApp
A1 NO AUTHENTICATE failed.
javax.mail.AuthenticationFailedException: AUTHENTICATE failed.
at com.sun.mail.imap.IMAPStore.protocolConnect(IMAPStore.java:732)
at javax.mail.Service.connect(Service.java:366)
at javax.mail.Service.connect(Service.java:246)
at uk.co.rpa.TestApplication.readEmailsOutlook(TestApplication.java:162)
at uk.co.rpa.TestApplication.main(TestApplication.java:104)
javax.mail.AuthenticationFailedException: AUTHENTICATE failed.
I give all access you provided, but still coming, please can you provide whole code.
Your code worked for me . Thanks a lot !!!