Blazor WASM - .NET Core 3.1 API UserInfo

So, I have figured out how to pass the information inside the access token from WASM (using spa app with code PKCE) to the WEB API.

However, that creates a security problem if someone changes the group claims of a user. As long as the user spa app is open in a browser, it will have the same token and group claims passed to the api. I have tested this scenario.

I am trying to figure out how when a send a bare minimum access_token which I can do that has the userid in it. But I am trying to figure out how I can gain access to the the access token sent and the userid so I can then validate the token and call userinfo on my own which contains the groups the user is apart of, for each web api call that is made using a client spa app for okta? So I notice the sdk addoktamvc but that requires the okta app to be a web app which it is not. The AddOktaWebApi works capturing the information in the user identity claims but I need to go out and get the rest of the information under userinfo that has a custom claim attached for authorization that lists the groups so I can populate the roles in the identity or even possibly the new policy part of .net core.

Anyone point me in the right direction? I am sure it is probably something obvious but I am missing it in my searches.

My SPA and My web api are not under the same subdomain. So the spa is app.mydomain.com and the web api is webapi.mydomain.com.

Code I have in my ConfigureServices method in startup.cs

services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme;
options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme;
options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme;
}).AddOktaWebApi(
new OktaWebApiOptions()
{
OktaDomain = Configuration.GetValue(“Okta:OktaDomain”),
AuthorizationServerId = “default”
}
);
services.AddAuthorization();

Disappointed that no one could help point me in a direction on this… But after researching more and more I figured it out. Figured I post incase someone finds it and needs the answer.

Sorry for the editing but the preformatted text in this editor kind s bad and doesn’t work well.

FOR WEB API

Need a class

    public class OktaJwtBearerEvents : JwtBearerEvents
    {
        private IConfiguration _configuration;
        public OktaJwtBearerEvents(IConfiguration configuration)
        {
            _configuration = configuration;
        }
        public override async Task TokenValidated(TokenValidatedContext context)
        {
            try
            {
                var userPrincipal = context.Principal;
                var claimsIdentity = (ClaimsIdentity)userPrincipal.Identity;

                var userId = userPrincipal.Claims.FirstOrDefault(c => c.Type == "uid")?.Value;

                var client = new OktaClient(new OktaClientConfiguration
                {
                    OktaDomain = _configuration["Okta:Domain"],
                    Token = _configuration["Okta:ApiKey"]
                });

                var userInfo = await client.Users.GetUserAsync(userId);
                claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, userInfo.Profile.DisplayName));
                claimsIdentity.AddClaim(new Claim(ClaimTypes.Email, userInfo.Profile.Email));
                claimsIdentity.AddClaim(new Claim("login", userInfo.Profile.Login));
                var groups = await userInfo.Groups.ToListAsync();

                // Assign Roles
                foreach (var group in groups)
                    claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, group.Profile.Name));

            }
            catch (Exception ex)
            {
                throw;
            }
        }
    }

.NET Core 3.1 Web API Startup.cs

                services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
            {
                options.SaveToken = true;
                options.Authority = Configuration["Okta:Authority"];
                options.Audience = "api://default";
                options.EventsType = typeof(OktaJwtBearerEvents);
            });

            services.AddAuthorization();

            services.AddTransient<OktaJwtBearerEvents>();

WASM

Need a two classes

        public class IasAppWebApiAuthMsgHandler : AuthorizationMessageHandler
        {
            public IasAppWebApiAuthMsgHandler(IAccessTokenProvider provider,
                NavigationManager navigationManager)
                : base(provider, navigationManager)
            {
                ConfigureHandler(
                    authorizedUrls: new[] { "http://localhost:58095/api/" },
                    scopes: new[] { "openid"});
            }
        }

    public class RolesClaimsPrincipalFactory : AccountClaimsPrincipalFactory<RemoteUserAccount>
    {
        public RolesClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor) : base(accessor)
        {
        }
        public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
            RemoteUserAccount account, RemoteAuthenticationUserOptions options)
        {
            var user = await base.CreateUserAsync(account, options);
            if (!user.Identity.IsAuthenticated)
            {
                return user;
            }

            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(claim => claim.Type == "groups");
            if (roleClaims == null || !roleClaims.Any())
            {
                return user;
            }

            foreach (var existingClaim in roleClaims)
            {
                identity.RemoveClaim(existingClaim);
            }

            var rolesElem = account.AdditionalProperties["groups"];
            if (!(rolesElem is JsonElement roles))
            {
                return user;
            }

            if (roles.ValueKind == JsonValueKind.Array)
            {
                foreach (var role in roles.EnumerateArray())
                {
                    identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
                }
            }
            else
            {
                identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
            }

            return user;
        }
    }

Program.cs

            builder.Services.AddScoped<IasAppWebApiAuthMsgHandler>();
            builder.Services.AddHttpClient("ServerAPI",
                    client => client.BaseAddress = new Uri("http://localhost:58095/api/"))
                    .AddHttpMessageHandler<IasAppWebApiAuthMsgHandler>();

            builder.Services.AddOidcAuthentication(options =>
            {
                options.ProviderOptions.ResponseType = "code";

                options.UserOptions.RoleClaim = "role";
                builder.Configuration.Bind("Okta", options.ProviderOptions);

            }).AddAccountClaimsPrincipalFactory<RolesClaimsPrincipalFactory>();

Now to get the WASM to work you need to create an spa application. then go into your authorization server.
Add a claim named “groups” with a token type id token on UserInfo with value type groups and regexp match on .* on any scope. --> this is for the WASM
Create an API Token in your API | Token area this is API key you will use.

Make you appsettings.json for both applications reflecting the data needed for the above code.

You will need the Okta.Sdk nuget package.

Hope this helps someone else in the future…

–Angela

1 Like

Hi Angela! Sorry, we couldn’t get to you sooner, but we’re glad you were able to figure it out and kindly share your solution. Were you using any of our samples or blogs by chance? Definitely would love your feedback on how we can do better.


could you please explain more?

regards

1 Like

I believe @angela is referring to creating one of these API keys, so that you can call Okta endpoints

I’m new to Okta. Can somone please explain how to send a token from Blazor WASM to ASP.NET Core API and have the API validate the token? The docs and examples do not seem to mentioned this. Unless I’m missing it. I get how to hook up the Blazor app with Okta and the API with Okta, but how do I hook up my Blazor app with my API? I mean as far as the token from Okta goes.

Hello,
This sample app does exactly that. It will require one of our resource samples as well which you will find a link to on the documentation page.

Awesome! That showed me what I was missing. Working now, thanks.