My main question: Working with Exchange Online, how is the streaming subscription supposed to work in relation to the periodic access token refresh?
Details:
I have been using EWS streaming subscriptions for some time for on-premise Exchange. Following the usual process I create a StreamingSubscriptionConnection
, add the mailbox subscriptions to it, and then open the connection. When the OnDisconnect
event fires, we re-open the connection.
We have now implemented the streaming subscription for mailboxes in Exchange Online, which requires us to use Modern Auth with Microsoft.Identity.Client. As advised on this page, we are forcing a refresh of the access token every 30 minutes on a background thread.
Everything works great, until we refresh the access token. At that point, when a connection's OnDisconnect
event fires, and we attempt to re-open the connection, it fails with 401 Unauthorized. We currently have it re-assigning the Credentials
property of the ExchangeService
object, after refreshing the token, but that doesn't seem to have any effect on the StreamingSubscriptionConnection
objects which were created prior to refreshing the token.
Note the StreamingSubscriptionConnection
class doesn't have a Credentials
property that we can reset after the token refresh. How is the connection object supposed to get the updated credentials?
It turns out I was not actually updating the Credentials
property of the ExchangeService
object after refreshing the token. Once I got that sorted, the re-opening of the connection works fine.
For anyone who might be interested, here's how we're doing this. First define the scopes:
private string[] ewsScopes = new string[] { "https://outlook.office.com/.default" };
Now get the access token:
_app = ConfidentialClientApplicationBuilder.Create(_EXOAppId)
.WithAuthority(AzureCloudInstance.AzurePublic, _EXOTenantId)
.WithClientSecret(_EXOClientSecret)
.Build();
TokenCacheHelper.EnableSerialization(_app.AppTokenCache);
_EXOAuthResult = await _app.AcquireTokenForClient(ewsScopes).ExecuteAsync();
Then setup a timer to periodically refresh the token (we're doing it every 30 minutes):
_nextRefresh = DateTime.Now.AddMinutes(refreshInterval);
_refreshTimer = new System.Timers.Timer();
_refreshTimer.Interval = 1000;
_refreshTimer.Elapsed += _refreshTimer_Elapsed;
_refreshTimer.Start();
And the timer Elapsed event:
private async void _refreshTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (DateTime.Now < _nextRefresh) return;
_refreshTimer.Stop();
_EXOAuthResult = await _app.AcquireTokenForClient(ewsScopes)
.WithForceRefresh(true) // Do NOT force refresh every time!!
.ExecuteAsync();
//--> Here is where you would update the Credentials property of your ExchangeService
_nextRefresh = DateTime.Now.AddMinutes(refreshInterval);
_refreshTimer.Start();
}
That's basically it. You just need to make sure you're caching the token properly, which should be done by the TokenCacheHelper.