Home > Mobile >  Azure AD Client Credentials and Interactive in same ASP.NET Core API
Azure AD Client Credentials and Interactive in same ASP.NET Core API

Time:01-31

I have an ASP.NET Core (3.1) web API with a couple of client UI apps. Authentication is via Azure AD, everything is working, using:

services.AddAuthentication()
    .AddAzureADBearer(options => Configuration.Bind("AzureAD", options))

I also want to allow machine to machine API access using the Client Credentials flow. I can also get this working using the same app registration.

However, I need a way to validate which flow the request is using, as I want to expose functionality using Client Credentials API to API requests that I don't want interactive users to have access to.

What is the best way to make this work?

I have created a separate app registration in AAD that the Client Secret for the Client Credentials grant is on, and I have it adding permissions (as roles) to the token. And in the app registration for the API, I have granted permission to the Client Credentials app registration. But if I obtain a token with this flow, I can't authenticate. I have found that changing the scope in the token request to match the scope on the API app registration gives me a token that allows me to access the API, but then it is missing the app roles.

One the interactive token there are some user specific claims. So one workaround would be to check for the presence of these claims and disallow the functionality I want to restrict if they are present, but this seems a little hacky.

What else can I do? Is there a way to make both login flows work? Or another option that I've missed?

CodePudding user response:

In case anyone else needs to get this working, I got it working by switching from:

services.AddAuthentication()
    .AddAzureADBearer(options => Configuration.Bind("AzureAD", options))

to:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.MetadataAddress = $"https://login.microsoftonline.com/mydomain.onmicrosoft.com/.well-known/openid-configuration";
        options.TokenValidationParameters.ValidateAudience = false;
    });

There are some additional steps too, as mentioned in the question. I created a separate app registration in AAD, and in the app registration for the API granted permission to the new app registration. In the new app registration I had to edit the manifest to get the scope I wanted included as a role (scopes are only assigned to user tokens, not tokens obtained with the client credentials grant).

With the token working that has the role data, for requests to my restricted endpoint I can just check that it's there:

public bool ValidateScope(string scopeName)
{
    return _httpContextAccessor.HttpContext.User.IsInRole(scopeName);
}

bool authorised = _clientCredentialsService.ValidateScope("restricted");

if (!authorised)
{
    throw new UnauthorizedAccessException("Attempt to access restricted functionality as a regular user");
}

(I have a filter that picks up this exception and bubbles it up to the consumer as a 403).

If anyone else is doing this you can see I've set ValidateAudience to false, so you probably want to add some policies if you do this.

  •  Tags:  
  • Related