6
votes

My web service is currently doing basic username/password authentication in order to subscribe the exchange user for receiving the events (like new mail event etc) like below:

var service = new ExchangeService(exchangeVersion)
                                  {
                                      KeepAlive = true,
                                      Url = new Uri("some autodiscovery url"),
                                      Credentials = new NetworkCredential(username, password)
                                  };

var subscription = service.SubscribeToPushNotifications(
                                    new[] { inboxFolderFoldeID },
                                    new Uri("some post back url"),
                                    15,
                                    null,
                                    EventType.NewMail,
                                    EventType.Created,
                                    EventType.Deleted,
                                    EventType.Modified,
                                    EventType.Moved,
                                    EventType.Copied);

Now, I am supposed to replace the authentication mechanism to use OAuth protocol. I saw some examples but all of them seem to be talking about authenticating the client (https://msdn.microsoft.com/en-us/library/office/dn903761%28v=exchg.150%29.aspx?f=255&MSPPError=-2147217396) but nowhere I was able to find an example of how to authenticate an exchange user with OAuth protocol. Any code sample will help a lot. Thanks.

3
One more thing that came up while investigating oauth for EWS, is it only available for office 365 and not for exchange servers. msdn.microsoft.com/en-us/library/office/… says that "OAuth authentication for EWS is only available in Exchange as part of Office 365. EWS applications require the "Full access to user's mailbox" permission."tavier
unfortunately none of the links talks about how to get it to work with EWS managed API. The msdn url I posted in the question does not seem to authenticate the user but just authorizing the client it seemstavier
yes, but this seems to be based on REST APIs and I want to use EWS manged APIs if possible. But if not, I guess this would be my last option.tavier

3 Answers

5
votes

It's not clear what you mean with 'web service' and how you currently get the username and password. If that is some kind of website where the user needs to login or pass credentials, then you'll have to start an OAuth2 grant from the browser as in redirecting the clients browser to the authorize endpoint to start implicit grant or code grant. The user will be presented a login screen on the OAuth2 server (and not in your application), once the user logs in a code or access token (depending on the grant) will be returned to your application which you can use in the ExchangeService constructor.

If that 'web' service is some service that runs on the users computer you can use one of the methods described below.

Get AccessToken using AuthenticationContext

The example seems to be based on an older version of the AuthenticationContext class.

The other version seems to be newer, also the AcquireToken is now renamed to AcquireTokenAsync / AcquireTokenSilentAsync.

No matter which version you're using, you will not be able to pass username and password like you're doing in your current code. However, you can let the AcquireToken[Async] method prompt for credentials to the user. Which, let's be honest, is more secure then letting your application deal with those user secrets directly. Before you know, you'll be storing plain text passwords in a database (hope you aren't already).

In both versions, those methods have a lot of overloads all with different parameters and slightly different functionality. For your use-case I think these are interesting:

Prompt behavior auto, in both vesions, means: the user will be asked for credentials when they're not already cached. Both AuthenticationContext constructors allow you to pass a token-cache which is something you can implement yourself f.e. to cache tokens in memory, file or database (see this article for an example file cache implementation).

Get AccessToken manually

If you really want to pass in the user credentials from code without prompting the user, there is always a way around. In this case you'll have to implement the Resource Owner Password Credentials grant as outlined in OAuth2 specificatioin / RFC6749.

Coincidence or not, I have an open-source library called oauth2-client-handler that implements this for use with HttpClient, but anyway, if you want to go this route you can dig into that code, especially starting from this method.

Use Access Token

Once you have an access token, you can proceed with the samples on this MSDN page, f.e.:

var service = new ExchangeService(exchangeVersion)
                  {
                      KeepAlive = true,
                      Url = new Uri("some autodiscovery url"),
                      Credentials = new OAuthCredentials(authenticationResult.AccessToken))
                  };
3
votes

In case someone is still struggling to get it to work. We need to upload a certificate manifest on azure portal for the application and then use the same certificate to authenticate the client for getting the access token. For more details please see: https://blogs.msdn.microsoft.com/exchangedev/2015/01/21/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow/

0
votes

Using the example code in this Microsoft Document as the starting point and these libraries:

  • Microsoft Identity Client 4.27
  • EWS Managed API v2.2

I am able to successfully authenticate and connect with Exchange on Office 365.

      public void Connect_OAuth()
      {
         var cca = ConfidentialClientApplicationBuilder
               .Create          ( ConfigurationManager.AppSettings[ "appId" ] )
               .WithClientSecret( ConfigurationManager.AppSettings[ "clientSecret" ] )
               .WithTenantId    ( ConfigurationManager.AppSettings[ "tenantId" ] )
               .Build();

         var ewsScopes = new string[] { "https://outlook.office365.com/.default" };

         AuthenticationResult authResult = null;

         try
         {
            authResult = cca.AcquireTokenForClient( ewsScopes ).ExecuteAsync().Result;
         }
         catch( Exception ex )
         {
            Console.WriteLine( "Error: " + ex );
         }

         try
         {
            var ewsClient = new ExchangeService();

            ewsClient.Url                = new Uri( "https://outlook.office365.com/EWS/Exchange.asmx" );
            ewsClient.Credentials        = new OAuthCredentials( authResult.AccessToken );
            ewsClient.ImpersonatedUserId = new ImpersonatedUserId( ConnectingIdType.SmtpAddress, "[email protected]" );

            ewsClient.HttpHeaders.Add( "X-AnchorMailbox", "[email protected]" );


            var folders = ewsClient.FindFolders( WellKnownFolderName.MsgFolderRoot, new FolderView( 10 ) );

            foreach( var folder in folders )
            {
               Console.WriteLine( "" + folder.DisplayName );
            }
         }
         catch( Exception ex )
         {
            Console.WriteLine( "Error: " + ex );
         }
      }

The Microsoft example code did not work - the async call to AcquireTokenForClient never returned.

By calling AcquireTokenForClient in a separate try catch block catching a general Exception, removing the await and using .Result, this now works - nothing else was changed.

I realise that this is not best practice but, both with and without the debugger, the async call in the original code never returned.

In the Azure set-up:

  • A client secret text string was used - a x509 certificate was not necessary
  • The configuration was 'app-only authentication'

Hope this helps someone avoid hours of frustration.