I want to use Oauth2 authentication for my app. I want to use EWS Java apis to fetch data from O365. Is it possible? Document http://blogs.msdn.com/b/exchangedev/archive/2014/09/24/10510847.aspx talks about fetching oauth token for REST apis should I use same document to fetch token to be used with EWS web services also? Can anyone share any code sample doing this with java.
3 Answers
It is possible. You have to register your app in the same way as you do for REST, but you need to specify the special EWS permission "Have full access via EWS to users' mailboxes". You'll need to do the OAuth flow to retrieve the access token, then include that in the Authorization header in your EWS requests. I don't have a Java sample for you, but those are the basic steps required.
I know, the question is quite old but the answers & comments still helped me out today. So, I want to briefly wrap it up:
In this pull-request: https://github.com/OfficeDev/ews-java-api/pull/321 the header validation was removed as described in the comments of the accepted answer.
So, its enough to set the token via
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
service.getHttpHeaders().put("Authorization", "Bearer " + officeToken);
Don't set any additional credentials.
For completeness: In my scenario, the officeToken is retrieved on client side via the Office JavaScript api
Office.initialize = function() {
$(document).ready(function (){
Office.context.mailbox.getCallbackTokenAsync(function(result) {
result.value; // is the officeToken of above
// do s.th. with the officeToken; e.g. send it to the server
});
});
});
On the server, we can now get the content of a mail. In the newest version of the Office JavaScript Api, this is also possible directly in the client. However, your Exchange Api version must be 1.3. So, this solution with retrieving the token and sending it to a server is useful in case your Exchange server runs an older version.
Given that using basic auth with EWS will cease to work Oct 2020(source), I went down the path of getting my app to use OAuth token authentication instead.
As mentioned by Jason Johnson, you're required to allow the Azure AD application 'Full access via EWS to users mailboxes'. As you can imagine, this creates a security concern since the application can access and modify anyones mailbox in that tenant. Use with great care!
Disclaimer - adal4j is no longer supported and while this solution works note that the adal4j library has a bug which will incorrectly log errors in
AdalCallable.java. This fork patches the issue but no public artifact is available, so you'll need to compile it yourself. Another option may be to try the more up-to-date msal4j however I haven't tested this solution with that library.
Here are the maven dependencies I used, I excluded slf4j since I had a class loader clash in glassfish so the exclusion is optional:
<dependency>
<groupId>com.microsoft.ews-java-api</groupId>
<artifactId>ews-java-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>adal4j</artifactId>
<version>1.6.4</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
Here is the token provider:
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.enterprise.concurrent.ManagedExecutorService;
import org.apache.log4j.Logger;
import com.microsoft.aad.adal4j.AuthenticationCallback;
import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;
import microsoft.exchange.webservices.data.core.ExchangeService;
import microsoft.exchange.webservices.data.core.WebProxy;
import microsoft.exchange.webservices.data.core.enumeration.misc.ConnectingIdType;
import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
import microsoft.exchange.webservices.data.misc.ImpersonatedUserId;
/**
* Used to obtain an access token for use in an EWS application. Caches the
* token and refreshes it 5mins prior to expiration.
*
* @author Stephen O'Hair
*
*/
public final class MsEwsTokenProvider {
private static final Logger log = Logger.getLogger(MsEwsTokenProvider.class);
private static final String EWS_URL = "https://outlook.office365.com/EWS/Exchange.asmx";
private static final String RESOUCE = "https://outlook.office365.com";
private static final String TENANT_NAME = "enter your tenant name here";
private static final String AUTHORITY = "https://login.microsoftonline.com/" + TENANT_NAME;
private static final long REFRESH_BEFORE_EXPIRY_MS = Duration.ofMinutes(5).toMillis();
private static long expiryTimeMs;
private static String accessToken;
/**
* Takes an OAuth2 token and configures an {@link ExchangeService}.
*
* @param token
* @param senderAddr
* @param traceListener
* @param mailboxAddr
* @return a configured and authenticated {@link ExchangeService}
* @throws URISyntaxException
* @throws Exception
*/
public static ExchangeService getAuthenticatedService(String token, String senderAddr,
TraceListener traceListener) throws URISyntaxException, Exception {
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
service.setTraceListener(traceListener);
service.getHttpHeaders().put("Authorization", "Bearer " + token);
service.getHttpHeaders().put("X-AnchorMailbox", senderAddr);
//service.setWebProxy(new WebProxy(proxyHost, proxyPort));
service.setUrl(new URI(EWS_URL));
service.setImpersonatedUserId(new ImpersonatedUserId(ConnectingIdType.PrincipalName, senderAddr));
return service;
}
/**
* Simple way to get an access token using the Azure Active Directory Library.
*
* Authenticates at : https://login.microsoftonline.com/
*
* @param clientId
* - client id of the AzureAD application
* @param clientSecret
* - client secret of the AzureAD application
* @param service
* - managed executor service
*
* @return provisioned access token
* @throws MalformedURLException
* @throws InterruptedException
* @throws ExecutionException
* @throws TimeoutException
*/
public static synchronized String getAccesToken(String clientId, String clientSecret, ManagedExecutorService service)
throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException {
long now = System.currentTimeMillis();
if (accessToken != null && now < expiryTimeMs - REFRESH_BEFORE_EXPIRY_MS) {
AuthenticationContext context = new AuthenticationContext(AUTHORITY, false, service);
AuthenticationCallback<AuthenticationResult> callback = new AuthenticationCallback<AuthenticationResult>() {
@Override
public void onSuccess(AuthenticationResult result) {
log.info("received token");
}
@Override
public void onFailure(Throwable exc) {
throw new RuntimeException(exc);
}
};
log.info("requesting token");
Future<AuthenticationResult> future = context.acquireToken(RESOUCE,
new ClientCredential(clientId, clientSecret), callback);
// wait for access token
AuthenticationResult result = future.get(30, TimeUnit.SECONDS);
// cache token and expiration
accessToken = result.getAccessToken();
expiryTimeMs = result.getExpiresAfter();
}
return accessToken;
}
}
Here is an example using those token provider class above to list messages from an inbox and send an email:
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import com.microsoft.aad.adal4j.AuthenticationCallback;
import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;
import microsoft.exchange.webservices.data.core.ExchangeService;
import microsoft.exchange.webservices.data.core.WebProxy;
import microsoft.exchange.webservices.data.core.enumeration.misc.ConnectingIdType;
import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
import microsoft.exchange.webservices.data.misc.ImpersonatedUserId;
/**
* Entry point.
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// Pro tip: make sure to set your proxy configuration here if needed
// and exclude outlook.office365.com from proxy SSL inspection.
String clientId = "your AzureAD application client id";
String clientSecret = "your AzureAD application client secret";
String tenantName = "your tenant";
String recipientAddr = "[email protected]";
String senderAddress = "[email protected]";
TraceListener traceListener = new ITraceListener() {
@Override
public void trace(String traceType, String traceMessage) {
// TODO log it, do whatever...
}
};
// I used a ManagedExecutorService provided by glassfish but you can
// use an ExecutorService and manage it yourself.
String token = MsEwsTokenProvider.getAccesToken(clientId, clientSecret, service);
// don't log this in production!
System.out.println("token=" + token);
// test mailbox read access
System.out.println("geting emails");
try (ExchangeService service = MsEwsTokenProvider.getAuthenticatedService(token, senderAddress)) {
listInboxMessages(service, senderAddress);
}
// send a message
System.out.println("sending a message");
try (ExchangeService service = getAuthenticatedService(token, senderAddress, traceListener)) {
sendTestMessage(service, recipientAddr, senderAddress);
}
System.out.println("finished");
}
public static void sendTestMessage(ExchangeService service, String recipientAddr, String senderAddr)
throws Exception {
EmailMessage msg = new EmailMessage(service);
msg.setSubject("Hello world!");
msg.setBody(MessageBody.getMessageBodyFromText("Sent using the EWS Java API."));
msg.getToRecipients().add(recipientAddr);
msg.send();
msg.setSender(new EmailAddress(senderAddr));
}
public static void listInboxMessages(ExchangeService service, String mailboxAddr) throws Exception {
ItemView view = new ItemView(50);
Mailbox mb = new Mailbox(mailboxAddr);
FolderId folder = new FolderId(WellKnownFolderName.Inbox, mb);
FindItemsResults<Item> result = service.findItems(folder, view);
result.forEach(i -> {
try {
System.out.println("subject=" + i.getSubject());
} catch (ServiceLocalException e) {
e.printStackTrace();
}
});
}