How to handle expired access tokens for MicrosoftGraphProvider

I am running into an issue with the MicrosoftGraphProvider where the access token will expire after about an hour, which according to MSFT is by design. I know the provider does not handle this process automatically so I came here looking for help.

Here is what I have done so far:

  1. In my scope I added offline_access, which will provide the refresh_token in the payload. This token gets stored automatically with the access token in the database.
  2. I have written code to return the new access token, but don’t know where it should be stored so my requests to the graphclient will still be valid.

Here is a sample of that code

    var refreshToken = provider.Items["refresh_token"];
     var baseUrl = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";

     var accessTokenParams = $"client_id={clientId}&client_secret={clientSecret}&refresh_token={refreshToken}&grant_type=refresh_token";

     var contents = await baseUrl.SendStringToUrlAsync(method:HttpMethods.Post, requestBody:accessTokenParams, contentType: MimeTypes.FormUrlEncoded);

Is there a certain method in IAuthRepositoryAsync that I can pass the new token?

OAuth tokens aren’t used by ServiceStack after Authentication and doesn’t update them thereafter, you’d just update the locations where you’re accessing them from, e.g. if it’s just stored in the session, update & save the session.

The only API to update the UserAuthDetails in the Auth Repository is CreateOrMergeSession/Async APIs. But given this is an App feature I would instead be persisting the active token on a Custom UserAuth table that you update instead.

Thanks for the help. I wrap every request in a Polly Retry Policy and renew the token if the ServiceException is thrown. Seems to work fine.

1 Like

I am using the following code to retrieve the session from the request per the documentation:

var sessionKey = SessionFeature.GetSessionKey(request.SessionId); 
var session = await Request.GetCacheClientAsync()
            .GetAsync<IAuthSession>(sessionKey);

However the access token in the cache is still the older value. What do I need to do to get my changes to persist to the CacheEntry table?

Here is the code I use to update the access token

        var session = SessionAs<CustomUserSession>();
    
        var clientId = AppSettings.GetString($"oauth.microsoftgraph.AppId");
        var clientSecret =  AppSettings.GetString($"oauth.microsoftgraph.AppSecret");
        var baseUrl = AppSettings.GetString("oauth.microsoftgraph.TokenUrl");

        var token = session.ProviderOAuthAccess.First(x => x.Provider=="microsoftgraph");

        var refreshToken = token.Items["refresh_token"];
    
        var accessTokenParams = $"client_id={clientId}&client_secret={clientSecret}&refresh_token={refreshToken}&grant_type=refresh_token";

        var contents = await baseUrl.SendStringToUrlAsync(method:HttpMethods.Post, requestBody:accessTokenParams, contentType: MimeTypes.FormUrlEncoded);

        var authInfo = (Dictionary<string,object>)JSON.parse(contents);

        token.Items = authInfo.ToStringDictionary();

        await AuthRepositoryAsync?.CreateOrMergeAuthSessionAsync(session, token)!;

I added

await Request.GetCacheClientAsync()
    .ReplaceAsync<IAuthSession>($"urn:iauthsession:{session.Id}",session);

Which should update the session. Will see how it goes.

Sessions should be saved with their SaveSession/Async APIs.