azureazure-ad-b2cblazorblazor-server-side

Confused on how to get access tokens from B2C in Blazor App


I have a Blazor Server Side app configured with B2C auth. This app will call a webapi to do any of the data exchanges with my service. B2C auth works fine, and straight from template the config B2C auth is:

services.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
            .AddAzureADB2C(options => { Configuration.Bind("AzureAdB2C", options); });

The claims only has the claims I'm returning from my signin policy, without any access tokens I can use for auth on behalf to my Web api (Also secured with same B2C tenant).

I've read about 100 different docs, but it seems that nothing makes sense in the context of blazor. Is there anyone that has done this before that could shed some light?

First prize would be to request an access token once the user auths to B2C the first time, and then keep the token in cache to use in the blazor app for any api calls while the session / browser is open or the access token is valid.

It seems that this is the right path: https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi/blob/master/TaskWebApp/Controllers/TasksController.cs but what I'm not understanding is:

Thanks!


Solution

  • I was able to solve this myself. My AcquireTokenSilent call was failling because there was no users in the cache when I call it, so I had to make sure to add first entry to the cache when my user logs in. I was able to achieve this by configuring my auth as follows:

    services.AddAuthentication(sharedOptions =>
                {
                    sharedOptions.DefaultScheme = AzureADB2CDefaults.AuthenticationScheme;
                    sharedOptions.DefaultChallengeScheme = AzureADB2CDefaults.OpenIdScheme;
                })
                   .AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options))
                   .AddCookie();
    
                services.Configure<OpenIdConnectOptions>(AzureADB2CDefaults.OpenIdScheme, options =>
                {
                    //Configuration.Bind("AzureAdB2C", options);
                    options.ResponseType = Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectResponseType.CodeIdToken;
                    options.Scope.Add("offline_access");
                    options.Scope.Add("https://mytenant.onmicrosoft.com/api/api.read.write");
    
                    options.SaveTokens = true;
                    options.GetClaimsFromUserInfoEndpoint = true;
    
                    options.Events.OnAuthorizationCodeReceived = async context =>
                {
                    AzureADB2COptions opt = new AzureADB2COptions();
                    Configuration.Bind("AzureAdB2C", opt);
                    // As AcquireTokenByAuthorizationCodeAsync is asynchronous we want to tell ASP.NET core that we are handing the code
                    // even if it's not done yet, so that it does not concurrently call the Token endpoint. (otherwise there will be a
                    // race condition ending-up in an error from Azure AD telling "code already redeemed")
                    context.HandleCodeRedemption();
    
                    var code = context.ProtocolMessage.Code;
                    string signedInUserID = context.Principal.FindFirst(ClaimTypes.NameIdentifier).Value;
    
                    IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder.Create(opt.ClientId)
                    .WithB2CAuthority(opt.Authority)
                    .WithRedirectUri(opt.RedirectUri)
                    .WithClientSecret(opt.ClientSecret)
                    .WithClientName("myWebapp")
                    .WithClientVersion("0.0.0.1")
                    .Build();
                    new MSALStaticCache(signedInUserID, context.HttpContext).EnablePersistence(cca.UserTokenCache);
    
                    try
                    {
                        AuthenticationResult result = await cca.AcquireTokenByAuthorizationCode(opt.ApiScopes.Split(' '), code)
                            .ExecuteAsync();
                        context.HandleCodeRedemption(result.AccessToken, result.IdToken);
    
                    }
                    catch (Exception)
                    {
    
    
                    }
    
                };
    
    
    
    
    
                });