-1

So I am working on a Blazor Server project that will use AzureAD B2C for identity. I have successfully configured the application and login/out/register all work correctly. The question that I have concerns using the returned user data in the application. In particular, I would like to access the Display Name and UserID data in order to link the user to data in my DB. What is the best method of accessing this data? I'm used to MVC where there is a User object that contains this data, but I'm not sure how to access that data using the new platform. I'm sure I've missed something simple.

Another question would be how to structure a link to the edit profile and reset password functions. There are plenty of examples on login/out, but not the rest of the functions.

I've included a link to my repo below. The relevant code is in the GameManager.WebApp project.

Thanks in advance for the help.

Project Repo

  • have you referred to https://learn.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview#claims? – Jim Xu Nov 16 '20 at 09:04
  • 1
    I have. That documentation discusses the tokens in good detail, but it doesn't include any information about using them in a Blazor application specifically. –  Nov 16 '20 at 15:45
  • Is that you use user flow to do auth? If so, have you configured application claims in the flow? – Jim Xu Nov 17 '20 at 00:52
  • 1
    Since the project was still new, I recreated it from a template and will figure it out as I go. –  Nov 18 '20 at 15:49

2 Answers2

3

To get the Display name in Azure AD B2C you need to get it as an optional claim. If you are working with the built in userflow select the user flow and go to user attribute there we can see the optional claims like below

enter image description here

In the Blazor server application we get the claims information in the @context.User. The user id you can get it from the id token with in the sub claim. To get the token you need to register OIDC middleware inside ConfigureServices and set SaveTokens to true :

services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
        .AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{       
    options.SaveTokens = true;
});

And refer to this code sample : https://stackoverflow.com/a/59901672/5751404 to save tokens to localstorage for later use .

Please reference this sample for Blazor Server apps that use EF Core.

Raghavendra beldona
  • 1,937
  • 1
  • 4
  • 9
1

Based on that feedback, I have created a solution that works well for me. I started by adding authentication in Startup.cs which takes the connection options from appsettings.json

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
            .AddMicrosoftIdentityWebApp(options =>
            {
                Configuration.Bind("AzureAdB2C", options);
                options.SaveTokens = true;
            });

I then added an ApplicationUser.cs POCO to hold the information that I was interested in for my users

public class ApplicationUser
{
    public string UserId { get; set; }
    public string EmailAddress { get; set; }
    public string DisplayName { get; set; }
    public string Country { get; set; }
}

To populate that user object, I created a _Host.cshtml.cs file in the Pages folder with the following:

public class HostAuthenticationModel : PageModel
{
    public IActionResult OnGet()
    {
        if (User.Identity.IsAuthenticated)
        {
            ClaimsPrincipal user = HttpContext.User;

            ApplicationUser.UserId = user.Claims
                .Where(claim => claim.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier")
                .FirstOrDefault().Value.ToString();
            ApplicationUser.Country = user.Claims
                .Where(claim => claim.Type == "country")
                .FirstOrDefault().Value.ToString();
            ApplicationUser.EmailAddress = user.Claims
                .Where(claim => claim.Type == "emails")
                .FirstOrDefault().Value.ToString();
            ApplicationUser.DisplayName = user.Claims
                .Where(claim => claim.Type == "name")
                .FirstOrDefault().Value.ToString();
        }
        return Page();
    }

    public ApplicationUser ApplicationUser { get; set; } = new ApplicationUser();
}

This code gets the User from the HttpContext and maps the claims to my user object class. This could be extended as needed.

With that in place, I added a model directive to the top of the _Host.cshtml file

@model HostAuthenticationModel

and added a tag helper to the Component

<component 
    type="typeof(App)" 
    render-mode="ServerPrerendered" 
    param-ApplicationUser="Model.ApplicationUser" 
/>

That tag helper passes the new ApplicationUser object into the app component as a parameter.

To use that parameter, I needed to get it inside of the App component as follows

@code {
[Parameter]
public ApplicationUser ApplicationUser { get; set; }
}

I then created a cascading parameter with that value in App so I can easily access it from any child component

<CascadingValue Value="@ApplicationUser">
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingValue>

Finally, in the component where I needed the User object, you simply declare the cascading parameter thusly

[CascadingParameter] ApplicationUser ApplicationUser { get; set; }

It is probably possible to add the ApplicationUser object into a state service of some sort and provide that through DI, but I haven't taken it to that extreme just yet (but probably will in the future at some point).