Blazor Server- AD B2C - Public, User and Admin sections with simple .net core identity authentication / authorization technique

Chad Bennett 1 Reputation point
2021-12-06T04:25:38.993+00:00

Update - I now believe combining @Michael Washington Azure AD Groups solution with my custom AuthorizationHandler solution is the best of both worlds. I don't have an example of it, but if you are going down this path, look in to it.

Edited Heavily as question "evolved"
Ecom / Blazor Server site - Need a simple way to do non-authenticated public pages, user specific profile pages and authorized only admin pages. Different page elements will be visible depending on Authentication and Authorization status. Would like to use AD B2C and asp.net core identity decoration for simplicity (ex - [Authorize(Roles = "Administrator")]).

To allow public pages anyone can access -> remove the default policies in Program.cs

To only show pages or elements to authenticated users -> use [Authorize] and AuthorizeView

To do b2c, VERY basic roll authorization, create a custom user attribute field on your B2C tenant (ex. isAdmin) and return it on all User Flows. In your code, create a custom AuthorizationHandler that checks the Claims token data returned from the UserFlow for the custom attribute. Then declare the custom Handler in the Program.cs and give it a custom policy declaration. You can now use it just like any .net core identity decoration (or so I think).

Sample code

-- Create a CustomAuthorization.cs class file --

// poc example to match a user name to give page / component authorization   
  
public class CustomAuthorization : IAuthorizationRequirement   
{  
    public string _myUserName;  
  
    public CustomAuthorization(string myUserName)  
    {  
        _myUserName = myUserName.ToLower();  
    }  
}  
  
public class myCustomAuthorizationRequirementHandler : AuthorizationHandler<CustomAuthorization>  
{  
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorization requirement)  
    {  
        string? LoggedInName = context.User.Identity?.Name; // example of getting the display name  
  
        var FirstName = context.User.Claims.FirstOrDefault(  
            c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname")?.Value; // example of getting the given name  
  
        if (LoggedInName is null) { }  
  
        else if (LoggedInName.ToLower() == requirement._myUserName) // useless example of checking if the display name equals the requirement  
        {  
            context.Succeed(requirement);  
        }  
        return Task.CompletedTask;  
    }  
}  

-- In Program.cs --

// in the "AddAuthorization" section add

options.AddPolicy("UserNameDodger", policy => policy.Requirements.Add(new CustomAuthorization("Dodger")));  

//under that section add

builder.Services.AddSingleton<IAuthorizationHandler, myCustomAuthorizationRequirementHandler>();     

-- On any RazorPage --
@attribute [Authorize(Policy = "UserNameDodger")]

---------
Still missing:

  • how to set a custom b2c attribute for the user (may have to use MS graph similar to ADefWebserver's example below but hopefully I can manually set it in Azure).
  • how to redirect someone if they reach a location they need authorization for but don't have it (currently just says "not authorized")

HUGE thanks to @Michael Washington and many others who helped me get to this solution. I could completely be missing something, but I think this is going to work really well for me.

Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,385 questions
Microsoft Entra External ID
Microsoft Entra External ID
A modern identity solution for securing access to customer, citizen and partner-facing apps and services. It is the converged platform of Azure AD External Identities B2B and B2C. Replaces Azure Active Directory External Identities.
2,639 questions
{count} votes

4 answers

Sort by: Most helpful
  1. Michael Washington 911 Reputation points MVP
    2021-12-06T14:10:48.557+00:00

    With https://blazorsimplesurvey.azurewebsites.net/ you don't have to log in until you click the Log in link. The source code is here: https://github.com/ADefWebserver/BlazorSimpleSurvey/

    Normally in the Program.cs file you have code like this:

    builder.Services.AddAuthorization(options =>
    {
        // By default, all incoming requests will be authorized according to the default policy
        options.FallbackPolicy = options.DefaultPolicy;
    });
    

    However, in Blazor Blogs, it's Program.cs file does not have this line:

    options.FallbackPolicy = options.DefaultPolicy;
    

    Also, this is the code for the Log in link:

     <a href="MicrosoftIdentity/Account/SignIn">Log in</a>
    

    This is the code for the Log out link:

    <a href="MicrosoftIdentity/Account/SignOut">Log out</a>
    
    1 person found this answer helpful.

  2. Zhi Lv - MSFT 32,011 Reputation points Microsoft Vendor
    2021-12-07T02:35:49.08+00:00

    Hi @Chad Bennett ,

    Don't require Authentication until Authorization check needed (ex. don't authenticate on Index page)

    If you don't require authentication and authorization for some pages, and allow anonymous to access them, you could try to use the AllowAnonymous attribute:

    Add the following Razor code to the Authentication component under its @Anonymous directive.

    @using Microsoft.AspNetCore.Components.WebAssembly.Authentication  
    @attribute [AllowAnonymous]  
    

    Reference: Require authorization for the entire app


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,
    Dillion

    0 comments No comments

  3. Siegfried Heintze 1,861 Reputation points
    2021-12-24T16:20:02.47+00:00

    I'm sorry to see that you are struggling so much with this... I've been struggling too...

    I wonder if I wrote a book on what I have learned about AAD/B2C if anyone would buy it? It gets even uglier when you try to port your ASP.NET AAD/B2C to a kubernetes replica set! No examples anywhere! (well last I checked a year and a half ago)...

    It is very frustrating that Microsoft does not document this! I have already complained to the Microsoft AAD evangelists on the discord channel that AAD/B2C does not get enough love and they agreed.

    1. Too bad you guys did not see this earlier post where Alfredo help me learn about custom attributes months ago: how-to-add-authorization-to-basice-aadb2c-web-app.html. Now I don't understand why you need a custom authorization handler... Is there something unique to Blazor that requires this?
    2. And here is the answer to your remaining question: Stan help me figure out how to write scripts to manage my users attributes here:how-to-populate-azure-active-directory-b2c-user-custom-user-attributes
    3. As you can see here how-to-use-role-based-authorization-with-net-core.html, it would be nice if the MS employees were aware of using custom attributes to implement AAD/B2C authorization.
    4. Hmm... I'll be watching to see if you figure out the redirect question. Oh wait, Alfredo already figured that out too: Here is the redirect page: AccessDenied.cshtml and here are the attributes in the controller: HomeController.cs.

    I think you are all set and ready to go (unless you want to run this in a kubernetes replica set)...

    Good Luck...

    0 comments No comments

  4. Aria Dreamer 0 Reputation points
    2023-08-19T21:31:58.7333333+00:00
    It seems like you're looking for guidance on setting up authentication and authorization in a Blazor Server application using Azure AD B2C and ASP.NET Core Identity. Your description includes the steps for achieving this, and I'll provide a summary and clarification on some of the points you've mentioned.
    
    1. Public Pages:
    For non-authenticated public pages, you can simply allow anonymous access to those pages. In your Razor page, you can use the AllowAnonymous attribute:
    
    csharp
    Copy code
    @attribute [AllowAnonymous]
    2. Authenticated User Pages:
    To show pages or page elements only to authenticated users, you can use the [Authorize] attribute. This attribute can be applied to entire pages or specific components within pages. You can also use the AuthorizeView component to conditionally render content based on the user's authentication status:
    
    csharp
    Copy code
    <AuthorizeView>
        <Authorized>
            <!-- Content for authenticated users -->
        </Authorized>
        <NotAuthorized>
            <!-- Content for non-authenticated users -->
        </NotAuthorized>
    </AuthorizeView>
    3. Authorization Using Custom Claims:
    It looks like you want to implement basic role-based authorization using custom claims returned by Azure AD B2C. You can achieve this by creating custom claims in your B2C policies and then using an AuthorizationHandler to check for those claims in the user's identity.
    
    Your CustomAuthorization class and myCustomAuthorizationRequirementHandler class demonstrate how you can implement this custom authorization logic. You're checking for a specific custom claim (e.g., isAdmin) and using it to determine whether the user has the required authorization.
    
    4. Setting Custom B2C Attributes:
    To set a custom B2C attribute for a user, you would typically do this during the user registration or profile update process. B2C provides REST APIs and libraries like Microsoft Graph API that allow you to manage user profiles and attributes. You can set the custom attribute in the user's profile using these APIs.
    
    5. Handling Unauthorized Access:
    To handle cases where a user reaches a location they need authorization for but don't have it, you can configure a custom page to be displayed when authorization fails. This is done by setting up the AccessDeniedPath in your Startup.cs:
    
    csharp
    Copy code
    services.ConfigureApplicationCookie(options =>
    {
        options.AccessDeniedPath = "/AccessDenied"; // Specify your custom access denied page
    });
    In your custom access denied page, you can provide a user-friendly message and perhaps a link back to the home page or a different action.
    
    6. Credits to Other Contributors:
    It's great that you acknowledge the help you've received from the community, including references to other solutions like @Michael Washington's Azure AD Groups solution. Collaborative efforts and sharing of knowledge are essential in the developer community.
    
    Remember that implementing security features like authentication and authorization requires careful consideration of security best practices to ensure the protection of your application and user data. Always test your implementation thoroughly and keep your security mechanisms up to date.
    
    Please note that this answer is based on the information you provided, and I recommend referring to official documentation and resources for the most accurate and up-to-date guidance.
    
    0 comments No comments