1
votes

I have an application that uses EWS streaming subscriptions via the managed API (built from latest source on GitHub as NuGet version is out of date), and have been enhancing it to group subscriptions for mailboxes with the same GroupingInformation and ExternalEwsUrl user settings, to reduce the number of connections, as described in Maintain affinity between a group of subscriptions and the Mailbox server in Exchange.

I am also introducing modern authentication for Exchange Online: Authenticate an EWS application by using OAuth

I have only been testing the changes on a relatively small Azure tenant. When I try to create a subsequent subscription on a group, it works perfectly with basic authentication, but with OAuth authentication, it always fails with HTTP error 500. The EWS error message is "Request failed because EWS could not contact the appropriate CAS server for this request".

I include an excerpt from the XML trace, when using OAuth, for the request and response for the first subscription on the anchor mailbox, then the request and failed response for the second subscription. The GroupingInformation value for the two mailboxes was "VE1PR03" when these requests were made.

It is not obvious how the use of OAuth should affect the routing of the requests.

POST /EWS/Exchange.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept: text/xml
User-Agent: MyApp/2.2.0.20149 (ExchangeServicesClient/2.2.1.0)
Accept-Encoding: gzip,deflate
X-AnchorMailbox: user1@xyz.onmicrosoft.com
X-PreferServerAffinity: true
Authorization: Bearer ey..
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <t:RequestServerVersion Version="Exchange2016" />
    <t:ExchangeImpersonation>
      <t:ConnectingSID>
        <t:SmtpAddress>user1@xyz.onmicrosoft.com</t:SmtpAddress>
      </t:ConnectingSID>
    </t:ExchangeImpersonation>
  </soap:Header>
  <soap:Body>
    <m:Subscribe>
      <m:StreamingSubscriptionRequest>
        <t:FolderIds>
          <t:DistinguishedFolderId Id="inbox">
            <t:Mailbox>
              <t:EmailAddress>user1@xyz.onmicrosoft.com</t:EmailAddress>
            </t:Mailbox>
          </t:DistinguishedFolderId>
        </t:FolderIds>
        <t:EventTypes>
          <t:EventType>NewMailEvent</t:EventType>
        </t:EventTypes>
      </m:StreamingSubscriptionRequest>
    </m:Subscribe>
  </soap:Body>
</soap:Envelope>

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Encoding: gzip
Vary: Accept-Encoding
X-CalculatedFETarget: VI1P194CU002.internal.outlook.com
X-BackEndHttpStatus: 200,200
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Set-Cookie: exchangecookie=5bf5f04d41dd4205b2fd96f211c5b2b4; expires=Thu, 17-Jun-2021 14:06:18 GMT; path=/; secure; HttpOnly
Set-Cookie: X-BackEndOverrideCookie=VE1PR03MB5854.eurprd03.prod.outlook.com~1943309328; path=/; secure; HttpOnly
Server: Microsoft-IIS/10.0
X-FEProxyInfo: VI1P194CA0032.EURP194.PROD.OUTLOOK.COM
X-CalculatedBETarget: VE1PR03MB5854.eurprd03.prod.outlook.com
X-RUM-Validated: 1
x-ms-appId: f456225c-aef6-41fc-bbd5-8a5c9c9287d6
X-FromBackend-ServerAffinity: True
x-EwsHandler: Subscribe
X-AspNet-Version: 4.0.30319
X-BeSku: WCS5
X-DiagInfo: VE1PR03MB5854
X-BEServer: VE1PR03MB5854
X-Proxy-RoutingCorrectness: 1
X-Proxy-BackendServerStatus: 200
X-FEServer: VI1P194CA0032,LO2P265CA0158
X-Powered-By: ASP.NET
Date: Wed, 17 Jun 2020 14:06:18 GMT
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <h:ServerVersionInfo MajorVersion="15" MinorVersion="20" MajorBuildNumber="3088" MinorBuildNumber="29" Version="V2018_01_08" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
  </s:Header>
  <s:Body>
    <m:SubscribeResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
      <m:ResponseMessages>
        <m:SubscribeResponseMessage ResponseClass="Success">
          <m:ResponseCode>NoError</m:ResponseCode>
          <m:SubscriptionId>JwB2ZTFwcjAzbWI1ODU0LmV1cnByZDAzLnByb2Qub3V0bG9vay5jb20QAAAADJevxSPm2ESq94+CIcSMEp28L5vHEtgIEAAAAONzlukW2B5KmN/hjFV/so0=</m:SubscriptionId>
        </m:SubscribeResponseMessage>
      </m:ResponseMessages>
    </m:SubscribeResponse>
  </s:Body>
</s:Envelope>

POST /EWS/Exchange.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept: text/xml
User-Agent: MyApp/2.2.0.20149 (ExchangeServicesClient/2.2.1.0)
Accept-Encoding: gzip,deflate
X-AnchorMailbox: user1@xyz.onmicrosoft.com
X-PreferServerAffinity: true
Cookie: X-BackEndOverrideCookie=VE1PR03MB5854.eurprd03.prod.outlook.com~1943309328
Authorization: Bearer ey..
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <t:RequestServerVersion Version="Exchange2016" />
    <t:ExchangeImpersonation>
      <t:ConnectingSID>
        <t:SmtpAddress>user2@xyz.onmicrosoft.com</t:SmtpAddress>
      </t:ConnectingSID>
    </t:ExchangeImpersonation>
  </soap:Header>
  <soap:Body>
    <m:Subscribe>
      <m:StreamingSubscriptionRequest>
        <t:FolderIds>
          <t:DistinguishedFolderId Id="inbox">
            <t:Mailbox>
              <t:EmailAddress>user2@xyz.onmicrosoft.com</t:EmailAddress>
            </t:Mailbox>
          </t:DistinguishedFolderId>
        </t:FolderIds>
        <t:EventTypes>
          <t:EventType>NewMailEvent</t:EventType>
        </t:EventTypes>
      </m:StreamingSubscriptionRequest>
    </m:Subscribe>
  </soap:Body>
</soap:Envelope>

HTTP/1.1 500 Internal Server Error
X-CalculatedFETarget: VI1P194CU002.internal.outlook.com
X-BackEndHttpStatus: 500,500
X-FEProxyInfo: VI1P194CA0034.EURP194.PROD.OUTLOOK.COM
X-CalculatedBETarget: VE1PR03MB5854.eurprd03.prod.outlook.com
X-RUM-Validated: 1
x-ms-appId: f456225c-aef6-41fc-bbd5-8a5c9c9287d6
X-BeSku: WCS5
X-DiagInfo: VE1PR03MB5854
X-BEServer: VE1PR03MB5854
X-Proxy-RoutingCorrectness: 1
X-Proxy-BackendServerStatus: 500
X-FEServer: VI1P194CA0034,LO2P265CA0158
Content-Length: 839
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Date: Wed, 17 Jun 2020 14:06:18 GMT
Set-Cookie: exchangecookie=39b2d19d8e9740128573cb1af6358c33; expires=Thu, 17-Jun-2021 14:06:18 GMT; path=/; secure; HttpOnly
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">*</Action>
  </s:Header>
  <s:Body>
    <s:Fault>
      <faultcode xmlns:a="http://schemas.microsoft.com/exchange/services/2006/types">a:ErrorInvalidRequest</faultcode>
      <faultstring xml:lang="en-US">Request failed because EWS could not contact the appropriate CAS server for this request.</faultstring>
      <detail>
        <e:ResponseCode xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">ErrorInvalidRequest</e:ResponseCode>
        <e:Message xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">Request failed because EWS could not contact the appropriate CAS server for this request.</e:Message>
      </detail>
    </s:Fault>
  </s:Body>
</s:Envelope>
2

2 Answers

1
votes

After further investigation, I have resolved this by simply using the same ExchangeService object for each subscription in the group (I had been creating a new ExchangeService for each subscription). The grouping now works with both OAuth and basic authentication. The article on maintaining affinity does say "Create and use one ExchangeService object for the rest of the procedure", and I should have taken that more literally!

Before creating each subscription, including the first, one does of course need to set ExchangeService.ImpersonatedUserId to the SMTP address of the relevant mailbox user, and after creating the first subscription, add an assignment of the X-BackendOverrideCookie cookie value from the first subscription response to the HttpHeaders.

I hope this is useful for anyone else who is working with streaming subscriptions.

1
votes

To pass X-BackEndOverrideCookie to subsequent requests, either:

  • Use the same ExchangeService for each subscription in the same grouping. This handles X-BackEndOverrideCookie automatically.
  • Use a fresh ExchangeService, but manually copy X-BackEndOverrideCookie via ExchangeService's CookieContainer property.

I recommend the second approach for thread-safety. If your application is a long-running service, you will likely need a retry loop to deal with failed subscriptions.

To transfer X-BackEndOverrideCookie manually:

string backEndOverrideCookie =
    service1.CookieContainer.GetCookies(service1.Url)["X-BackEndOverrideCookie"]?.Value;
...
if (!string.IsNullOrWhiteSpace(backEndOverrideCookie))
    service2.CookieContainer.SetCookies(service2.Url, "X-BackEndOverrideCookie=" + backEndOverrideCookie);

Note: Assigning to Credentials resets the ExchangeService's CookieContainer, and for OAuth you will need to do this regularly. Fortunately there's a simple workaround:

var cookieContainer = service.CookieContainer;
service.Credentials = new OAuthCredentials(authenticationResult.AccessToken);
service.CookieContainer = cookieContainer;