3

I'm building a multi-user, multi-tenant app that will access the Microsoft Graph API on behalf of many users while they're offline.

Because the app uses the Microsoft Identity for external OIDC authentication, during the user's first login, I already requested the appropriate scopes/consents and received the access/refresh tokens issued by Microsoft. The tokens are saved in the database.

Now I want to use Microsoft.Graph which makes accessing the Graph API much easier, but it seems that a big part of this SDK was written to retrieve tokens, which I do not need because I already have the tokens.

I do, however, want the SDK client to automatically use the stored refresh token to obtain new access tokens, instead of having to manually handle the refreshing logic myself, if possible.

Upon research, I found that I can pass in an null for the AuthenticationProvider when constructing the GraphServiceClient, and manually attach bearer token to the client like this:

var graphClient = new GraphServiceClient(authenticationProvider: null);

var requestHeaders = new List<HeaderOption>() 
                  { new HeaderOption("Authorization", "Bearer " + myToken) };

var me = await graphClient.Me.Request(requestHeaders).GetAsync();

This does work and bypasses the client's token retrieval logic, but the problem now is that it won't handle access token expiration/renewal. So I have to use separate code to make sure I always pass in a non-expired access token, and renew the token myself using the refresh token if necessary.

I wonder if there is a way to customize GraphServiceClient such that it doesn't try to obtain tokens using separate OAuth2/OIDC flows, but instead knows how to find an existing refresh token stored in my database, and use that refresh token to manage its renewal/expiration logic like it does for tokents it receives with its regular flows.

The ideal flow works like this:

  1. Pass in a parameter (the user's ID in my database) to create a GraphServiceClient.

  2. The client uses this UserID to lookup the stored tokens in my database (EF Core).

  3. If the stored access token already expired, use the stored refresh token to get a new one, and update the tokens in the database.

Would this be possible with the SDK?

Your advice will be greatly appreciated.

thankyoussd
  • 1,875
  • 1
  • 18
  • 39
  • This is the most basic scenario that should be solved with a couple of lines, yet Microsoft failed to provide a simple solution. – Ashi Jun 08 '23 at 15:37

2 Answers2

1

You can implement the IAuthenticationProvider interface in your own custom class, then do the necessary token checking/refresh in the AuthenticateRequestAsync function. See https://github.com/microsoftgraph/msgraph-training-dotnet-core/blob/main/demo/GraphTutorial/Authentication/DeviceCodeAuthProvider.cs for an example.

I'd recommend looking at the MSAL library to handle all of the token logic for you. You can hook into it's token cache to serialize it how you want (to store it in your database, for example). In the example I linked I'm using MSAL, and you can see I don't have to do any checking for expired tokens, it's all handled for me.

Jason Johnston
  • 17,194
  • 2
  • 20
  • 34
  • Thanks. I came across this post on Github https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/460 When using the method you recommended here, I assume there would be a separate GraphServiceClient created for every user's API call? If my background service needs to make many calls concurrently (sync/update many user's graph data at set intervals etc), this approach likely won't scale well, right? – thankyoussd Feb 05 '21 at 18:27
  • No not necessarily. MSAL's caching mechanism keys the tokens to the user's identity - you have to specify which user to get a token for. So theoretically you could use a Graph client for multiple users. If you're not using MSAL, I'm assuming you already have a way to map user to correct token? – Jason Johnston Feb 05 '21 at 20:03
  • Yes my tokens are actually saved using ASP.NET Core Identity's ```IdentityUserToken``` mapped to a ```ASPNetUserTokens``` table, along with exp dates, which allows me to retrieve the token based on user's ID and provider. Right now I handle refresh/renewal logic using EF Core and this ```IdentityModel``` library developed by the Identity Server folks, against ```https://login.microsoftonline.com/common/v2.0/```. I wonder if MSAL would be faster and more reliable than my simple "if expiring, call token endpoint again" logic, which I use to get a token and pass to ```GraphServiceClient```. – thankyoussd Feb 05 '21 at 20:36
  • Try this project on github: https://github.com/microsoftgraph/msgraph-training-aspnetmvcapp – Lenard Bartha Aug 25 '22 at 16:12
1

You can use generate an access token and then use it to send an email. But unfortunately you have to implement an IAccessTokenProvider

public class TokenProvider : IAccessTokenProvider
{
    public string token { get; set; }
    public AllowedHostsValidator AllowedHostsValidator => throw new NotImplementedException();

    public Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object>? additionalAuthenticationContext = null, CancellationToken cancellationToken = default)
    {
        return Task.FromResult(token);
    }
}

Then you can generate a GraphServiceClient as follows:

    TokenProvider provider = new TokenProvider();
    provider.token = _companyEmailAccountViewModel.AuthToken;
    var authenticationProvider = new BaseBearerTokenAuthenticationProvider(provider);
    GraphServiceClient graphClient = new GraphServiceClient(authenticationProvider);
Ashi
  • 806
  • 1
  • 11
  • 22
  • I am appriciating your big support to this community I was down in one issue or creating small part of code This was an issue : https://stackoverflow.com/questions/76639275/microsoft-graph-sdk-c-create-subscription-throw-error-as-below-expirationdatet – Hardik Masalawala Jul 11 '23 at 09:21