Using multi-tenant AAD delegated APIs from different tenants

This post looks at implementing and using Azure AD multiple tenant applications from different tenants. A service principal needs to be created for the tenant using the multi-tenant API and consent needs to be given for the API scope. The API will accept tokens from different issuers which need to be validated. It is important that all tenants allowed to use the API are validated.

Code: https://github.com/damienbod/AadMutliApis

Setup API Azure App Registration

A multi-tenant API Azure App registration is used to expose the scope which is required to use the API. The API is implemented using ASP.NET Core and validates this claim in the JWT token as well as other claims. V2 tokens are required and only delegated access tokens are created from this App registration.

Implement the API

An ASP.NET Core application implements the API and the security logic validating the access token. An explicit list of issuers can use the API The API validates that a secret is required to authenticate and the client that requested the access token is also validated. The authorization handler validates that the token have a scope claim which means that it is a delegated access token (if produced by Azure AD). Great care has to be taken when using mutli-tenant app registrations because any tenant can use this but not any tenant should be allowed to use the API.

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        options.MetadataAddress = aadMetadataAddress;
        //options.Authority = issuert1;
        options.Audience = aud;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            ValidAudiences = new List<string> { aud },
            ValidIssuers = new List<string> { issuert1 }
        };
    });

services.AddSingleton<IAuthorizationHandler, ValidTenantsAndClientsHandler>();

services.AddAuthorization(policies =>
{
    policies.AddPolicy("ValidTenantsAndClients", p =>
    {
        // only delegated trusted  known clients allowed to use the API
        p.Requirements.Add(new ValidTenantsAndClientsRequirement());

        // Validate id of application for which the token was created
        p.RequireClaim("azp", azpClientId);

        // client secret = 1, 2 if certificate is used
        p.RequireClaim("azpacr", "1");
    });
});

services.AddControllers(options =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
});

The handler validates that the scope claim has the expected value. Together with the rest of the validation, it is possible the validate that the access token is intended for this API.

public class ValidTenantsAndClientsHandler 
	: AuthorizationHandler<ValidTenantsAndClientsRequirement>
{

    protected override Task HandleRequirementAsync(
		AuthorizationHandlerContext context, 
		ValidTenantsAndClientsRequirement requirement)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));
        if (requirement == null)
            throw new ArgumentNullException(nameof(requirement));

        var scopeClaim = context.User.Claims.FirstOrDefault(t => t.Type == "scope");

        if (scopeClaim != null)
        {
            var scopes = scopeClaim.Value.Split(" ", 
				StringSplitOptions.RemoveEmptyEntries);
				
            if (scopes.Any(t => t == "access_as_user"))
            {
                context.Succeed(requirement);
            }
        }

        return Task.CompletedTask;
    }
}

Setup Service Principal for other tenants

Now that the application is ready and the App registration exists, a service principal can be created for this Azure App registration in the target tenant.

Connect-AzureAD -TenantId '<UI-tenantId>'                                            

New-AzureADServicePrincipal -AppId 'AppId-from-multi-tenant-api'

Give consent in your tenant to the Enterprise applications

The service principal can be found in the Enterprise applications blade.

  1. Open the Enterprise Applications blade
  2. Find your enterprise application using the Guid ObjectId from the Powershell script
  3. Open the permissions blade
  4. Grant Admin consent if you require to use local tenant permissions

Using and consent

To use the UI application and the multi-tenant API, consent must be given, usually by a tenant administrator on behalf of all users in this tenant. Different consents screens are displayed depending on the Azure tenant policies and the person using the application.

Once consent ahs been given, this can be viewed in the API permissions of the Enterprise application created for the target tenant.

The extra step of explicitly allowing the tenants that can use the API has advantages. You can continue to use delegated scopes and implement token exchange protocols for the downstream APIs where required. This is a secure way of connecting software systems using different identity providers if implemented correctly. A disadvantage with the approach is that each admin must give consent to use the API on their tenant.

Links

https://stackoverflow.com/questions/60929155/how-to-create-service-principal-of-multi-tenant-application

3 comments

  1. […] Using multi-tenant AAD delegated APIs from different tenants [#.NET #.NET Core #ASP.NET Core #Azure #Azure AD #aad #aspnetcore #AzureAD #consent #Jwt #multiTenant] […]

  2. […] Using multi-tenant AAD delegated APIs from different tenants (Damien Bowden) […]

  3. […] Using multi-tenant AAD delegated APIs from different tenants – Damien Bowden […]

Leave a comment

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