Implementing OAuth2 APP to APP security using Azure AD from a Web APP

This article shows how to implement an API service and client in separate ASP.NET Core applications which are secured using Azure application permissions implemented in an Azure App registration. The OAuth client credentials flow is used to get an access token to access the API. Microsoft.Identity.Web is used to implement the client credentials (CC) flow. Microsoft.Identity.Client can also be used to implement this flow, or any OAuth client implementation.

Code: BlazorWithApis

The OAuth client credentials flow can be used to access services, where no user is involved and the client is trusted. This flow is used in many shapes and forms in Azure. The client application requires some type of secret to get an access token to use the secured API. In Azure, there are different ways of implementing this which vary in different names and implementations. The main difference with most of these implementations is how the secret is acquired and where it is stored. A certificate can be used as the secret or an Azure App registration secret. This secret can be stored in Azure Key Vault. Managed identities provide another way of implementing app to app secured access between Azure services and can be used to acquire access tokens using the Azure SDK. Certificate authentication can also be used to secure this security flow. This can be a little bit confusing, but as a solution architect, you need to know when and where this should be used, and not.

Delegated user access tokens or application client credential tokens

As a general rule, always use delegated user access tokens and not application access tokens if possible. You can reduce the permissions per user to the max. To acquire a user delegated access token, an identity must login somewhere using a UI. A user interaction flow is required for this. The delegated user access token can be requested using a scope for the identity. In Azure AD, the On Behalf Flow OBO can also be used to acquire further delegated user access tokens for downstream APIs. This is not possible in Azure AD B2C.

Scopes or Roles Permissions

In Azure, scope permissions are used for delegated user access tokens, not application permissions. App Roles can be used for application and / or delegated access. Roles can only be defined in Azure AD App registrations and not Azure AD B2C App registrations. To define an Azure App registration with application App Roles, you need to use an Azure AD App registration. This is very Azure specific and nothing to do with security standards. You still request a scope when using delegated or application flows, but not scope permissions when using application OAuth client credentials. More information about this can be found in the Microsoft docs:

Protected web API: Verify scopes and app roles

By using application security permissions, you give the client application permissions for whatever is allowed in the service. No user is involved. This cannot be reduced for different users, only for different client applications.

Azure App Registration setup

The hardest part of implementing an API protected using application permissions is to know how and where to setup the Azure App registration. The Azure App registration needs to be created in an Azure AD app registration and not an Azure AD B2C tenant, even if you use this. The Azure App registration needs an application ID URI, make sure this is created.

An Azure App Role can be created and can be validated in the access token.

The app role is defined as an application type. I named the role access_as_application.

The role can be added as a permission and admin consent can be given. This will be included in tokens issued for this Azure app registration.

API setup

The API is setup using the Microsoft.Identity.Web. The AddMicrosoftIdentityWebApiAuthentication adds the OAuth validation using the configuration from the app settings. I created an authorization policy to implement the authorization which is applied to the controller or as a global filter. I think this is the best way as it is the standard way in ASP.NET Core. You should avoid using the Azure claims directly in the business of the application. Microsoft.Identity.Web also provides some specific Azure helper methods which checks consent or validates the scope etc. It is important that only access tokens intended for this API should work and all other access tokens must be rejected.

services.AddSingleton<IAuthorizationHandler, HasServiceApiRoleHandler>();

services.AddMicrosoftIdentityWebApiAuthentication(Configuration);

services.AddControllers();

services.AddAuthorization(options =>
{
	options.AddPolicy("ValidateAccessTokenPolicy", validateAccessTokenPolicy =>
	{
		validateAccessTokenPolicy.Requirements.Add(new HasServiceApiRoleRequirement());

		// Validate id of application for which the token was created
		// In this case the UI application 
		validateAccessTokenPolicy.RequireClaim("azp", "2b50a014-f353-4c10-aace-024f19a55569");

		// only allow tokens which used "Private key JWT Client authentication"
		// // https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens
		// Indicates how the client was authenticated. For a public client, the value is "0". 
		// If client ID and client secret are used, the value is "1". 
		// If a client certificate was used for authentication, the value is "2".
		validateAccessTokenPolicy.RequireClaim("azpacr", "1");
	});
});

Using Microsoft.Identity.Web

One way of implementing a client is to use Microsoft.Identity.Web. The client and user of the application uses the OpenID Connect Code flow and a secret with some Azure specifics and once authenticated, the application can request an application token using the ITokenAcquisition interface and the GetAccessTokenForAppAsync method. The scope definition uses the /.default value with the application ID URL from the Azure App registration. This uses the client credentials flow. If the correct parameters are used, an access token is returned and the token can be used to access the API.

public class ServiceApiClientService
{
    private readonly IHttpClientFactory _clientFactory;
    private readonly ITokenAcquisition _tokenAcquisition;

    public ServiceApiClientService(
        ITokenAcquisition tokenAcquisition,
        IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
        _tokenAcquisition = tokenAcquisition;
    }

    public async Task<IEnumerable<string>?> GetApiDataAsync()
    {
        var client = _clientFactory.CreateClient();

        // CC flow access_as_application" (App Role in Azure AD app registration)
        var scope = "api://b178f3a5-7588-492a-924f-72d7887b7e48/.default"; 
        var accessToken = await _tokenAcquisition.GetAccessTokenForAppAsync(scope);

        client.BaseAddress = new Uri("https://localhost:44324");
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        var response = await client.GetAsync("ApiForServiceData");

        if (response.IsSuccessStatusCode)
        {
            var stream = await response.Content.ReadAsStreamAsync();

            var payload = await JsonSerializer.DeserializeAsync<List<string>>(stream);

            return payload;
        }

        throw new ApplicationException("oh no...");
    }
}

Notes

When using client applications and the client credentials flow, it is important to only share the secret or certificate with a trusted client. The client should implement this is a safe way so that it does not get stolen. If I own the client, I deploy the client to Azure (if possible) and use a Key Vault to persist the certificates or secrets. A managed identity is used to access the key vault. This way, no secret is stored in an unsecure way. Try to use delegated access tokens rather than application tokens. The delegated access token is issued to the user and the application and the authorization can be reduced. This can only be done if using a user interaction flow to authenticate. Azure App registrations use scopes for delegated access tokens and roles can be used for application permissions. With app to app flows, other ways also exist for securing this access.

Links

https://github.com/AzureAD/microsoft-identity-web

https://docs.microsoft.com/en-us/azure/active-directory/develop/

https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2

https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/tree/master/4-Call-OwnApi-Pop

https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-verification-scope-app-roles?tabs=aspnetcore

4 comments

  1. […] Implementing OAuth2 APP to APP security using Azure AD from a Web APP [#.NET Core #ASP.NET Core #Azure #Azure AD #Azure B2C #OAuth2 #Security #Client credentials #Microsoft.Identity.Client #Microsoft.Identity.Web] […]

  2. Robert · · Reply

    Have to disagree with the statement that delegate user access token is preferable. This just pushes user AuthZ down into microservice layers and causes AuthZ assets/scopes INTO the identity layer. IMHO recipe for disaster.

  3. […] Implementing OAuth2 APP to APP security using Azure AD from a Web APP […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.