Search code examples
asp.net-coreasp.net-core-webapiazure-managed-identityazure-webappsazure-api-apps

Restrict Access with Azure Managed Identity in .NET Core Web API


My Azure Web App is calling my Azure API App endpoint. Both app services are in the same Azure subscription and RG. Web App is a .NET Core Web Application and API App is a .NET Core Web API. Web App is using standard HttpClient class to call the API App endpoint.

I followed https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal%2Cdotnet and created a user-assigned managed identity. Then assigned this identity to the Azure API App with role as contributor.

Web App still not assigned with the mentioned managed identity, but it can still access this API App without throwing the expected un-authorized error.

My question is that how can I restrict that only this managed identity should access the mentioned API app? code middlweware or some other Azure settings?


Solution

  • Your API should be receiving a JWT token from your App. You need to configure your API to validate the token and reject ones that don't meet your requirements. You get some basic validation out of the box - the token must pass audience, issuer and signing certificate validation - but if you want to restrict it to a single identity then you need to tell it what that is.

    There are a number of ways you can go about this, and Policy based authz is worth a read so you know the the approach (https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-6.0).

    Your identity will have a unique, immutable identifier, the objectId of the managed identity. This gets transmitted on the subject claim on your JWT token, so you can lock down your API to that identity with a simple global policy

    In your API start up where you have an AddAuthorization call, you can add a policy like so

    services.AddAuthorization(options =>
    {
        options.AddPolicy("OnlyMatchingSubject", policy =>
            policy.RequireAssertion(context => context.User.HasClaim(c =>
                c.Type is ClaimConstants.Sub && c.Value == "your-unique-subject-guid")));
    
        options.DefaultPolicy = options.GetPolicy("OnlyMatchingSubject");
    });
    
    

    Here, we say that your token must have a sub claim, and it must be a particular value to pass (just get your objectId for your identity from the portal), and then make that the default authentication policy.

    Here's a full example for a dotnet 6 minimal API.

    using System.Security.Claims;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.Identity.Web;
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
    
    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy("OnlyMatchingSubject", policy =>
            policy.RequireAssertion(context => context.User.HasClaim(MatchesExpectedSubjectClaim())));
    
        var policy = options.GetPolicy("OnlyMatchingSubject");
        options.DefaultPolicy = policy;
        options.FallbackPolicy = policy;
    });
    
    var app = builder.Build();
    app.UseAuthentication();
    app.UseAuthorization();
    app.MapGet("/", () => "Hello World!");
    
    app.Run();
    
    Predicate<Claim> MatchesExpectedSubjectClaim()
    {
        return c => c.Type is "sub" && c.Value == "your-unique-subject-guid";
    }
    

    You'll need to set an AzureAd config in your settings, as per this documentation https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-configuration#config-file