Share via

Need help authenticating a ASP.NET Core Web API method for use as a custom authentication extension in Microsoft External Extra ID

Isaac Weston 0 Reputation points
2026-03-03T20:34:05.6033333+00:00

My organization is trying to setup custom authentication extensions in a Microsoft Entra External ID tenant's sign-in/sign-up user flow. We currently have an ASP.NET Core Web API setup with the methods we would like the extensions to call. However, we are having issues setting up authentication. The documentation we have been following on Microsoft Learn uses Azure Functions for the API methods, which does not align with our current setup, so we are trying to use Microsoft Identity to authenticate, but the connector throws an ambiguous error whenever we try to secure and authenticate the API methods.

First, here is how we have configured Microsoft Identity in our ASP.NET Core Web API, with the Microsoft.Identity.Web NuGet package installed:

appsettings.[environment].js

"AzureAd": {
  "Instance": "https://[Entra External ID Directory Domain].ciamlogin.com/",
  "TenantId": "[Tenant ID]",
  "ClientId": "[Client ID]",
  "ClientSecret": "[Secret]"// Used for Microsoft Graph authentication
  "Audience": "api://[API host URI]/[Tenant ID]",
  "Scopes": {
    "Read": [ "access_as_user" ],
    "Write": [ "access_as_user" ]
  },
  "AppPermissions": {
    "Read": [ "Identity.Access" ],
    "Write": [ "Identity.Access" ]
  }
},

Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
// ...

try
{
    var builder = WebApplication.CreateBuilder(args);
    // ..
    // Add authentication
    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
    // Add authorization
    builder.Services.AddAuthorization();
    // ...
    var app = builder.Build();
    // ...
    app.UseAuthentication();
    app.UseAuthorization();
    app.Run();
}
finally
{
    // ...
}

API Controller

using Microsoft.AspNetCore.Authorization;
// ...

[HttpPost]
[Authorize]
[Route("[Route]", Name = "[Name]")]
public async Task<IActionResult> GetClaimsAsync([FromBody] TokenIssuanceStartRequest request)
{
	// ...
}

Microsoft Entra External ID configuration:

Screenshot 2026-03-03 144657

Screenshot 2026-03-03 144920

Screenshot 2026-03-03 145242

Screenshot 2026-03-03 152127

Is there anything we're missing? Is there any documentation or guides that can help us with this scenario?

Microsoft Security | Microsoft Entra | Microsoft Entra External ID

1 answer

Sort by: Most helpful
  1. Raja Pothuraju 47,510 Reputation points Microsoft Employee Moderator
    2026-04-06T07:57:03.5733333+00:00

    Hello @Isaac Weston,

    Hey Isaac, it turns out nothing magic was hiding in the Azure-Functions examples – you can absolutely host your custom auth extension in an ASP NET Core Web API. The catch is that Entra External ID calls your API using the OAuth2 client-credentials (app-only) flow, so:

    • the inbound token has NO user (“scp”) scopes

    • it HAS application permissions (in the “roles” claim)

    You had defined only delegated scopes (“access_as_user”) in your API registration, and you’re validating for scp. The connector grabs an app‐only token, so there is no scp claim and your [Authorize] check fails with a generic auth error.

    Here’s what you need to do:

    1. Expose an Application Permission (App Role) on your API registration – In Azure Portal > App registrations > your API > “App roles” > “+ Create app role” – Allowed member types = “Applications” – Give it a value, e.g. Identity.Access
    2. Grant that App Role to your custom-extension application – In your extension’s App registration > “API permissions” > “+ Add a permission” > “My APIs” > select your API > choose the new “Identity.Access” application permission – Click “Grant admin consent”
    3. Update your ASP NET Core code to validate roles instead of scopes In Program.cs something like:
      
         builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
      
           .AddMicrosoftIdentityWebApi(options => {
      
              builder.Configuration.Bind("AzureAd", options);
      
              // tell the middleware to look for "roles" (app perms) not "scp"
      
              options.TokenValidationParameters.RoleClaimType = "roles";  
      
           });
      
         builder.Services.AddAuthorization(opts => {
      
           opts.AddPolicy("AppOnly", policy => 
      
             policy.RequireRole("Identity.Access"));
      
         });
      
         app.UseAuthentication();
      
         app.UseAuthorization();
      
         [ApiController]
      
         [Route("...")]
      
         public class MyController : ControllerBase {
      
           [HttpPost, Authorize(Policy="AppOnly")]
      
           public IActionResult GetClaims([FromBody] TokenIssuanceStartRequest req) { … }
      
         }
      
      
    4. Call it exactly the way Entra External ID will: POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token • grant_type=client_credentials • client_id={your-extension-app-id} • client_secret={secret} • scope=api://{your-api-app-id-uri}/.default Then pass that token in Authorization: Bearer {the-access-token}

    At that point your API will validate the JWT’s audience (your API’s App ID URI), issuer (your *.ciamlogin.com tenant), and the roles claim (“Identity.Access”) and return 200.

    Hope that gets you unblocked!

    Reference docs for more details:

    • Custom Auth Extensions overview:

    https://learn.microsoft.com/entra/external-id/customers/concept-custom-extensions

    • Protect Web APIs with app-roles (no scp):

    https://learn.microsoft.com/entra/identity-platform/scenario-protected-web-api-verification-scope-app-roles?tabs=aspnetcore

    • OAuth2 client credentials grant flow:

    https://learn.microsoft.com/entra/identity-platform/v2-oauth2-client-creds-grant-flow

    • Troubleshooting custom extensions:

    https://learn.microsoft.com/entra/identity-platform/custom-extension-troubleshoot

    Note: This content was drafted with the help of an AI system. Please verify the information before relying on it for decision-making.

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.