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

I have created a sample application with OpenId Connect and Blazor (Server) for you here https://github.com/yberstad/BlazorAuth. It is uses a SameSiteCookie and OpenId Connect.

  1. In the appsettings.json, fill in your Authority, ClientId and ClientSecret.
  2. Configure the redirect URi in Azure to http://localhost:62438/signin-oidc/
  3. Start up the app in debug mode
  4. Got to http://localhost:62438/api/openidconnect/login
  5. Login with Azure
  6. Got to http://localhost:62438/api/openidconnect/user, having a break point in the OpenIdConnectController.GetUser action, there you can see how to getting hold of the access token.

How to get access and refresh token:

var accessToken = await HttpContext.GetTokenAsync("access_token");
var refreshToken = await HttpContext.GetTokenAsync("refresh_token");

SameSiteCookie Info: https://brockallen.com/2019/01/11/same-site-cookies-asp-net-core-and-external-authentication-providers/

Getting Access Token: http://docs.identityserver.io/en/latest/quickstarts/5_hybrid_and_api_access.html#using-the-access-token

Having the tokens stored in a SameSiteCookie makes it only visible for the server, hence not saving and exposing it in an unsafe environment on the client. A SameSiteCookie is also safe for XSS.

Hope this helps.


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)
                {


                }

            };





            });