0

I have a simple ASPNET Core Razor Pages app + ASPNET Core API:

for example the API just has:

builder.Services
    .AddAuthentication(...)
    .AddMicrosoftIdentityWebApi()

Razor pages has:

builder.Services
    .AddMicrosoftIdentityWebApp(azureConfig)
    .EnableTokenAcquisitionToCallDownstreamApi(...)
    .AddInMemoryTokenCaches();

I have two app registrations to support that, one registration for my UI, and one registration for my API that exposes an API.

Azure App Registrations Blade

So I would like to roll a UI that my users can use to generate a client for their own uses. In my experience, when I see this feature in other apps I usually see they support client credentials only.

Imagine a UI like this:

Example Web UI for Dynamic Client Registration

Now imagine code behind like this when you click that "Create new client" button:

public void CreateClientForUser(CurrentUser currentUser)
{
    var graphClient = new GraphServiceClient(requestAdapter);

    var requestBody = new Application
    {
        DisplayName = "API Key for " + currentUser.DisplayName,
    };
    var result = await graphClient.Applications.PostAsync(requestBody);

    dbContext.UserApps.Add(new UserApp
    {
        UserId = currentUser.Id,
        AppId = result.AppId
    });
}

Finally, imagine in my API I had a OnTokenValidated event handler like this:

// Untested psudocode
services.Configure<JwtBearerOptions>(
    name: JwtBearerDefaults.AuthenticationScheme,
    configureOptions: jwtBearerOptions =>
{
    var existingOnTokenValidatedHandler = jwtBearerOptions.Events.OnTokenValidated;
    jwtBearerOptions.Events.OnTokenValidated = async tokenValidatedContext =>
    {
        await existingOnTokenValidatedHandler(tokenValidatedContext);

        var context = tokenValidatedContext.HttpContext.RequestServices.GetRequiredService<BOIDbContext>();

        var principal = tokenValidatedContext.Principal!;
        var claimsIdentity = (ClaimsIdentity)principal.Identity!;

        var objectidentifierClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier";
        var objectIdentifier = principal.Claims.FirstOrDefault(t => t.Type == objectidentifierClaimType);

        var appId = principal.Claims.FirstOrDefault(t => t.Type == "appId");

        if (appId != "Main RazorPages Web UI")
        {
            // This must be a users access token?
            var usersApp = context.UserApps.FirstOrDefault(x => x.AppId == appId);
            if(usersApp is null)
            {
                context.Fail($"No app id found in app-user mapping table.");
                return;
            }

            // Set the user id! Done!
            claimsIdentity.AddClaim(new Claim(ApplicationClaimTypes.UserId, usersApp.UserId.ToString()));
        }
    };
});

Is this approach OK? On most sites that offer API access, they usually have a UI that lets me generate client credentials clients. Should I be only allowing users to create an interactive client? If I did that, the access tokens for my API would not require me to maintain a user mapping table correct?

Victorio Berra
  • 2,760
  • 2
  • 28
  • 53

1 Answers1

0

You can asssign set the user as owner and move such mapping from the db. You can use the Add owner operation for that. If you want to verify if a user is owner of an application you can use the List ownedObjects opearation.

Regarding the credentials provided, that depends on their needs. If they want an SPA or public app no client credentials are required. But they are needed them if they want a web app or api. The best would be to generate a certificate so that it can be downloaded by the user and registered in the application. For the latter you can use the application: addKey operation.

Since you're abstracting app registration and for non social user accounts (they don't have access anyways) it may make sense to block acces to the Azure Portal, or disable app registration for those users and make your api create the app registrations using client credentials.

AlfredoRevilla-MSFT
  • 3,171
  • 1
  • 12
  • 18