This article looks at implementing authorization in Microsoft Entra External ID for customers (CIAM) using Azure AD delegated roles. The roles can be assigned to users or groups in an Azure Enterprise application.
Code: https://github.com/damienbod/EntraExternalIdCiam
Blogs in this series
In Azure AD it has been possible to use roles with users and groups to implement authorization and access controls for software solutions. This makes IAM access management easy to apply in the Azure cloud. This is not possible when using the existing Microsoft identity customer solution Azure AD B2C and is one of the new features added to Entra External ID CIAM.
This is really easy to implement. The required roles can be created in the Azure App registration in the App roles blade. The roles must be of type Users/Groups because the roles are only applied to delegated authenticated identities (human based).

The Azure App registration App roles can be assigned to users or groups. In larger Azure clouds, the roles would be assigned to dynamic security groups or standard security groups and the groups would be assigned with members or a user group with members to this permission group. Assigning roles to users directly will only work for very small user based systems. You can mix groups and users as required.

The roles can now be used in the ASP.NET Core application. This requires three steps:
- Authentication of the identity (user + application)
- Add the claims correctly to the claims principal in the ASP.NET Core application
- Use the claims for authorization using ASP.NET Core Policies
Using the default Microsoft.Identity.Web client, the first two steps can be implemented in a very simple way. The client package does the heavy lifting. Sometimes you need to fix the namespaces in the claims, but this usually works good.
The claims can be displayed for the authenticated user in the UI
public void OnGet()
{
var claims = User.Claims.ToList();
foreach(var claim in claims)
{
if(claim != null && claim.Type == "roles")
{
Roles.Add(claim.Value);
}
}
}
I just iterate through each role claim.
@{
foreach( var role in Model.Roles)
{
<p><b>Role:</b> @role</p>
}
}
When using the role claim for authorization, you should never use this directly. Always use an ASP.NET Core authorization policy in the application and map the claim role definition to the requirement in the policy. The keeps the authorization simple and defined in one place. The claims are definitions or descriptions about the identity. The descriptions should not be also used as authorization rules but should be used to help define the authorization rules. For more complex authorization logic, ASP.NET Core authorization handlers can be used to implement the logic.
ASP.NET Core Authorization using role claims
The authorization policies can be added in the AddAuthorization method. the policies can use a claim directly or use an ASP.NET Core AuthorizationHandler. All identity descriptions are mapped or used in the policies and handlers. Only the policies are used in the application.
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("UserPolicy", policy => {
policy.RequireClaim("roles", "user-role");
});
options.AddPolicy("AdminPolicy", policy => {
policy.RequireClaim("roles", "admin-role");
});
options.AddPolicy("UserAdminPolicy", policy => {
policy.AddRequirements(new UserAdminRequirement());
});
options.FallbackPolicy = options.DefaultPolicy;
});
If you have complex authorization business logic, this can be supported by implementing an AuthorizationHandler class. The Handler requires an IAuthorizationRequirement.
public class UserAdminRequirement : IAuthorizationRequirement {}
UserAdminHandler implements the AuthorizationHandler with the UserAdminRequirement. This checks if a identity has the required claim.
public class UserAdminHandler
: AuthorizationHandler<UserAdminRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
UserAdminRequirement requirement)
{
var userRole = context.User
.FindFirst(c => c.Type == "roles"
&& c.Value == "user-role");
var adminRole = context.User
.FindFirst(c => c.Type == "roles"
&& c.Value == "admin-role");
if (userRole != null || adminRole != null)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
The handler needs to be registered in the IoC.
builder.Services.AddSingleton<IAuthorizationHandler, UserAdminHandler>();
The authorization policies are applied to the pages.
[Authorize(Policy = "UserAdminPolicy")]
public class UserAdminModel : PageModel
When the application is started, the user can authenticate and the authorization is forced. In this test, the admin user is missing the role to view the user page. Normally you would also hide the link to the user page. This is usability, authorization can only be forced in trusted backends.

The admin page displays the business because to identity is authorized to view this.

Notes
Authorization with CIAM has all the power like Azure AD and makes it possible to use the same tools to manage the user, application and device identities. I really missed this in Azure AD B2C.
Links
https://learn.microsoft.com/en-us/azure/active-directory/external-identities/customers
https://learn.microsoft.com/en-us/azure/active-directory/external-identities/
https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-external-id
https://developer.microsoft.com/en-us/identity/customers
https://github.com/Azure-Samples/ms-identity-ciam-dotnet-tutorial