Qiuzman
Qiuzman

Reputation: 1761

Implementing a Cache service for Box API token in ASP.NET Core

So I have used OneDrive with MS Graph and it has built in functions by default as shown below to store the current token in cache so you do not need to request it each time as shown below in program.cs:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
            .AddMicrosoftIdentityWebApp(options =>
            {
                builder.Configuration.Bind("AzureAd", options);
            }).EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
                    .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
                    .AddInMemoryTokenCaches();

THe AddInMemoryTokenCaches() function for the MS Graph Identity sdk stores the token in cache for later use and also the sdk handles getting a new refresh token if it expires by doing a redirect. However, I do not see such a function for Box API and I am curious the best approach to doing something like this. I am sure this could come up in other scenarios not just Box. My current approach is I initiate a service for a interface for box in my program.cs

builder.Services.AddTransient<IBoxDrive, BoxDrive>();

Then in this interface and extension of it I use dependency injection to get the config file and then request a new token every request. This seems so inefficient but I am not sure how to cache it and also if the token does expire how do I ensure I get a new refresh token without just requesting it every time like I do not. My interface is as follows:

namespace TestProject.Repositories

{ public interface IBoxDrive { Task FileInfoAsync(string BoxId); }

public class BoxDrive : IBoxDrive
{
    private readonly IWebHostEnvironment _env;
    private readonly BoxJWTAuth _session;

    public BoxDrive(IWebHostEnvironment env) {
        var _env = env;
        var RootPath = _env.ContentRootPath;
        var boxConfigLocation = Path.Combine(System.IO.Directory.GetParent(RootPath).ToString(), "box/699422622_746zo0rh_config.json");
        var config = BoxConfigBuilder.CreateFromJsonString(System.IO.File.ReadAllText(boxConfigLocation)).Build();
        _session = new BoxJWTAuth(config);
    }

    public async Task<BoxFile> FileInfoAsync(string BoxId)
    {
        BoxFile file = null;
        try
        {
            var adminToken = await _session.AdminTokenAsync(); //valid for 60 minutes so should be cached and re-used
            BoxClient adminClient = _session.AdminClient(adminToken);

            file = await adminClient.FilesManager.GetInformationAsync(id: BoxId);

            return file;
        }
        catch (Exception ex2)
        {
            return file;
        }
    }

}

}

Upvotes: 0

Views: 476

Answers (1)

mwoda
mwoda

Reputation: 11

You're right that it's not just a scenarion common to Box SDK, but can be applied to other http clients that need to reuse tokens. What you probably want is some kind of token cache/storage that store your access token for different request.

Example implementation should include 2 methods - one to get the token and one to store it. In the simplest scenario, it can retrieve the token from memory.

public interface ITokenStorage
{
    string RetrieveToken();
    void SaveToken(string accessToken);
}

You can then register it as a singleton to reuse the same token across different requests

builder.Services.AddSingleton<ITokenStorage, TokenStorage>();

Back to BoxDrive. You need a method that will create a BoxClient and reuse an existing token or save a new one in case of a refresh. Fortunately, the BoxClient takes care of refreshing the token, so you only need to retrieve/save it. You can use the SessionAuthenticated event for it.

public BoxClient RegisterBoxClient(string rootPath, ITokenStorage tokenStorage)
{
    var accessToken = tokenStorage.RetrieveToken();

    var boxConfigLocation = Path.Combine(System.IO.Directory.GetParent(rootPath).ToString(), "config.json");
    var config = BoxConfigBuilder.CreateFromJsonString(System.IO.File.ReadAllText(boxConfigLocation)).Build();
    var jwtAuth = new BoxJWTAuth(config);
    var client = jwtAuth.AdminClient(accessToken);

    client.Auth.SessionAuthenticated += delegate (object o, SessionAuthenticatedEventArgs e)
    {
        string newAccessToken = e.Session.AccessToken;

        tokenStorage.SaveToken(newAccessToken);
    };

    return client;
}

Keep in mind that BoxJWT constructor requires a token. You can provide a dummy/expired one for a first api call. AFAIK tokenless constructor is currently supported only for CCG.

Then the BoxDrive code should look like this

private readonly IBoxClient _client;

public BoxDrive(IWebHostEnvironment env, ITokenStorage tokenStorage)
{
    _client = RegisterBoxClient(env.ContentRootPath, tokenStorage);
}

public async Task<BoxFile> FileInfoAsync(string BoxId)
{
    BoxFile file = null;
    try
    {
        var user = await _client.FilesManager.GetInformationAsync(id: BoxId);

        return file;
    }
    catch (Exception ex)
    {
        return file;
    }
}

You could also consider registering BoxClient as a singleton if that fits your use case.

Upvotes: 1

Related Questions