How to retrieve the id_token with identity integration in 8.4

Previously, I was able to get the app roles assigned to a Entra ID/Azure AD user using the following code from a Blazor Server app

new JwtAuthProvider(appSettings) {
         AuthKeyBase64 = appSettings.GetString("AuthKeyBase64")
    , PopulateSessionFilter = (session, jsonObject, request) =>
    {
        //For whatever reason the userauthName was matching the Id, which is didn't do before I activated this provider
        session.UserName = session.Email;
        session.UserAuthName = session.Email;
    }
    , CreatePayloadFilter = (jsonObject, session) =>
    {
        var tokens = session.GetAuthTokens();
        var accessToken = tokens.Where(x=> x.Provider == "microsoftgraph").FirstNonDefault();
        
        // if the id_token has been returned populate any roles
        var idTokens = JwtAuthProviderReader.ExtractPayload(accessToken.Items["id_token"]);
        if (!idTokens.ContainsKey("roles")) return;
        
        var roles = (idTokens["roles"] as List<object>).ConvertTo<List<string>>();
        if (roles.Count <= 0) return;
        jsonObject["roles"] = roles.ToJson();
        jsonObject["perms"] = roles.ToJson();

    }
}

However I am unable to decipher how to support similar functionality with the identity integration. Does anyone have any suggestions?

Check to see if they’re already populated in the HttpContext.User.Claims. I would’ve expected roles would be automatically populated in the populated HttpContext.User when using Azure AD.

With ASP .NET Identity Auth, Authentication is no longer implemented or controlled by ServiceStack, you’d need to consult Microsoft Entra/Azure AD docs or support for how to correctly configure it in your ASP .NET Core App.

Although it’s very surprising .NET 8 didn’t include a working Blazor + Azure ID/Entra configuration, but there are some good resources in that thread, e.g:

Its well documented Program.cs includes information about how to configure roles:

const string MS_OIDC_SCHEME = "MicrosoftOidc";
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(MS_OIDC_SCHEME)
  .AddOpenIdConnect(MS_OIDC_SCHEME, oidcOptions => {
    oidcOptions.Authority = "https://login.microsoftonline.com/{TENANT ID}/v2.0/";
    oidcOptions.ClientId = "{CLIENT ID}";
    oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
    oidcOptions.MapInboundClaims = false;
    oidcOptions.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
    oidcOptions.TokenValidationParameters.RoleClaimType = "roles";
  })
  .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);

There also does appear to be a .NET 9 Blazor + Entra Id sample App at:

See when I tried to add the OpenIDConnect authentication previously, SS would never log me in, it would just redirect me back to the login page again. I will try it again with this configuration and see if it works.

Thanks for the references. I have a sample MyApp from your site that lets me login without Identity enabled with help from the provided information. It is published here, GitHub - elbsoftwaresolutions/OpenIdRoleDemo: This demostrates my issue with roles not working with servicestack. My roles are persisting to the Authorize attributes on the razor pages but things are not working right on the SS side:

  1. User.GetRoles() returns no roles
  2. AuditBase cannot find my userAuthId or userAuthName - This might be due to IdentityServer not being used, so what options can I used from the users’ claims?
  3. ValidateIsRole attribute does not find my roles as well

This is an intranet application, so the end user doesn’t really need any of the identity related functionality.

We don’t have azure so we can’t run this project, but if you’re Authenticated with Identity Auth you should be Authenticated with ServiceStack. What are all the Claims for the Authenticated User with Roles?

They reside in the id_token, under “roles”{}

When I use the Identity Auth, after I try to save a update a booking I get this error:

 Error in GetSession() when ApplyPreAuthenticateFilters
      System.NotSupportedException: Claim 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' is required
         at ServiceStack.Auth.IdentityApplicationAuthProvider`2.PreAuthenticateAsync(IRequest req, IResponse res) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityApplicationAuthProvider.cs:line 123
         at ServiceStack.ServiceStackHost.ApplyPreAuthenticateFiltersAsync(IRequest httpReq, IResponse httpRes) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/ServiceStackHost.Runtime.cs:line 87
         at ServiceStack.ServiceExtensions.GetSessionInternalAsync(IRequest httpReq, Boolean reload, Boolean async, CancellationToken token) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/ServiceExtensions.cs:line 248

Here is a screenshot of the claims I have right before the code fails:
SCR-20241028-fmnm

So it seems that the claims from Azure have different names compared to Identity, which uses the SAML names.

Yeah it’s using a different type for the role claim, when it’s expecting roles use the ClaimTypes.Role type. First see if mapping inbound claims resolves it:

oidcOptions.MapInboundClaims = true;

Otherwise you should be able to use a IClaimsTransformation to add additional ClaimTypes.Role claims for each role.

I replaced all the OIDC code, with this simple section:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));

And now the roles map correctly!

The only issue now is the AutoApply for createdBy and modifiedBy are null for userAuthId and userAuthName. I also tried ${userSession.Email} with no luck. Any ideas?

The IdentityApplicationAuthProvider MapClaimsToSession dictionary lists which claims map to which User Session properties:

E.g. UserAuthId is populated from the ClaimTypes.NameIdentifier claim.

You can modify this dictionary with:

services.AddPlugin(new AuthFeature(IdentityAuth.For<ApplicationUser>(options =>
{
    //...
    options.ApplicationAuth(feature =>
    {
        feature.MapClaimsToSession["UserId"] = nameof(AuthUserSession.UserAuthId);
    });
});

Alternatively you can customize how the Session is populated with a PopulateSessionFilter, e.g:

options.ApplicationAuth(feature => {
    feature.PopulateSessionFilter = (session, principal, req) => {
        session.UserAuthId = principal.FindFirst(x => 
            x.Type == ClaimTypes.NameIdentifier)?.Value;
    };
});
1 Like

Thank you. That helped alot. I think I have patched something together that solves my problem. Would love a blazor template focused on intranet Entra Id sites that doesn’t need any of the Identity tables and infrastructure.