In this post, let's see how we can call an ASP.NET Core Web API Secured
with Microsoft Entra ID (known as Azure AD) using an Azure Managed Identity.
This is our scenario: we are going to have 2 APIs.
- Internal API: This API is going to expose a GET: /claims endpoint. This endpoint will return ClaimsPrinciple Claims and will require Authorization. It's secured with Microsoft Entra ID.
- Public API: This API is going to expose a GET: /claims endpoint, but this DO NOT require any Authorization. This API will have an Azure User-Assigned Managed Identity assigned and this endpoint will call the Internal APIs' GET: /claims using the Managed Identity.
I have created the following already.
- A User-Assigned Managed Identity with the name: mi-miauth-demo
-
Two brand new Azure App Services: app-miauth-internal-api and
app-miauth-internal-api. To the Public API, I have assigned the
User-Assigned Managed Identity: mi-miauth-demo.
Creating App Registration
The first step is creating an App Registration in Microsoft Entra ID. You can do so by going into Microsoft Entra ID -> App registrations -> + New registration. Give it a name, (in my case it's Managed Identity Auth Demo) and select an account type. In this case, for simplicity, I have selected Accounts in this organizational directory only. And then click on Create.
Once the App Registration is created (behind the scenes along with the App
Registration, a new Enterprise Application will get created with the same
name as in App Registration), I am clicking on Expose an API tab and
adding the Application ID URI.
App Registration -> Set Application ID URI |
Now go to App roles tab and create a new App role.
App Registration -> Create app role |
Configuring Enterprise Application (Service Principal)
Now we need to assign the Managed Identity access to the app role we created above. For that, I am running the following script from Windows PowerShell.
# Login to Azure and setting the subscription
Connect-AzAccount
Set-AzContext -SubscriptionId "<SubscriptionId>"
# Invoking Connect-MgGraph before any commands that access Microsoft Graph,
# Requesting scopes that we require during our session
$tenantID = '<TenantId>'
Connect-MgGraph -TenantId $tenantId -Scopes 'Application.Read.All', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All', 'Directory.AccessAsUser.All', 'Directory.Read.All', 'Directory.ReadWrite.All'
# App Registration Name
$appRegistrationName = 'Managed Identity Auth Demo'
# Install Microsoft.Graph Module if required using below command
# Install-Module Microsoft.Graph
# Retrieving Service Principal Id
$servicePrincipal = (Get-MgServicePrincipal -Filter "DisplayName eq '$appRegistrationName'")
$servicePrincipalObjectId = $servicePrincipal.Id
# Retrieving App role Id that the Managed Identity should be assigned to
$appRoleName = 'MI.Access'
$appRoleId = ($servicePrincipal.AppRoles | Where-Object {$_.Value -eq $appRoleName }).Id
# Managed Identity's Object (principal) ID.
$managedIdentityObjectId = '<ManagedIdentityObjectId>'
# Assign the managed identity access to the app role.
New-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId $servicePrincipalObjectId `
-PrincipalId $managedIdentityObjectId `
-ResourceId $servicePrincipalObjectId `
-AppRoleId $appRoleId
Once the commands are completed, go to the relevant
Enterprise Application -> Users and groups. Make sure you
can see the Role assignment we just did.
Enterprise Application -> Users and groups |
Coding
Now let's write some code for our APIs starting with the Internal API.
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web; using System.Security.Claims;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorizationBuilder();
WebApplication app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/claims", (ClaimsPrincipal claimsPrincipal) =>
{
return claimsPrincipal.Claims
.Select(claim => new
(
claim.Type,
claim.Value
))
.ToList();
})
.RequireAuthorization();
app.Run()
And update the appsettings.json adding AzureAd section as follows. Make sure to replace placeholders with your
values.
{
...
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "<Domain>",
"TenantId": "<TenantId>",
"ClientId": "<ClientId>" //Application (Client ID) of the App Registration
}
}
Now Internal API is all good. I am deploying the code changes.
We are almost there now. Let's step is calling the above endpoint from our public API endpoint.
using Azure.Core;
using Azure.Identity;
using System.Net.Http.Headers; using System.Text.Json.Nodes;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("/claims", async () =>
{
DefaultAzureCredential credential = new();
TokenRequestContext tokenRequestContext = new(new[]
{
"api://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx/.default" // Application ID URI
});
AccessToken accessToken = await credential.GetTokenAsync(tokenRequestContext);
HttpClient httpClient = new()
{
DefaultRequestHeaders =
{
Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Token)
}
};
string result = await httpClient.GetStringAsync("https://app-miauth-internal-api.azurewebsites.net/claims"); return JsonNode.Parse(result);
});
app.Run();
Public API is all good and I have deployed the code changes.
Now to the fun part, let's test the endpoints. Our expectation is the
endpoint in Internal API should return 401 and the endpoint in Public API
should return 200 with Claims of the Managed Idenity.
Just like that, we are getting the expected output. Note: You can see the ClaimPrinciple has the Role "MI.Access" we assigned, so you can extend your authorization policies.
Result |
Full code sample:
https://github.com/jaliyaudagedara/aspnetcore-managed-identity-auth
https://github.com/jaliyaudagedara/aspnetcore-managed-identity-auth
Hope this helps.
Happy Coding.
Regards,
Jaliya
No comments:
Post a Comment