Blazor Server App Authentication Notes

This document describes the setup for *Blazor Server* apps.

1. First, Some Definitions

A few key terms that will be beneficial to configuring a Blazor Server application for authentication and authorization:
Microsoft Identity platform

Was Azure Active Directory (Azure AD) but is now Microsoft Entra ID.
Used to authenticate and authorize users in web apps, web APIs, or apps that call protected web APIs.

Microsoft Identity Web authentication library

The GitHub project an be found here

Is a set of ASP.NET Core libraries that simplifies adding authentication and authorization support to web apps, web APIs, and daemon apps integrating with the Microsoft identity platform.

It provides a single-surface API convenience layer that ties together ASP.NET or ASP.NET Core, their authentication middleware, and the Microsoft Authentication Library (MSAL) for .NET that acquires tokens.

Learn more about it here.

OpenID Connect protocol

Used for handling authentication. More info about OpenID can be found here.

OAuth 2.0 protocol

Used for handling authorization. More info about OAuth 2.0 can be found here.

Authentication

The process of confirming that a user is the person that they say they are. The user is represented by an identity.

Authorization

After a user’s Identity has been confirmed, what are they allowed to access? Access is determined based on a combination of their Identity and the roles or claims associated with their Identity.

Role

A Role is simply a name like admin, supervisor, or content_manager. A user can have more than one role assigned to them which can then be checked by the application at runtime. The application can then authorize access to various application functionality based on these roles.

Claim

A Claim is an assertion by the user such as their name or email address. It can also be very specific to an aspect of the application such as canClickTheUpdateAccountButton. A Claim is like a Role but more granular. Claims are steadily becoming more favored over Roles.

App Role

Are custom roles to assign permissions to users or apps. The application defines and publishes the app roles and interprets them as permissions during authorization.

Follow this doc to setup app roles.

RBAC

Role-based access control (RBAC) is a popular mechanism to enforce authorization in applications. RBAC allows administrators to grant permissions to roles rather than to specific users or groups. The administrator can then assign roles to different users and groups to control who has access to what content and functionality.

By using RBAC with application role and role claims, developers can securely enforce authorization in their apps with less effort.

2. Summary

This document should be used with .Net 9.x projects.

3. Azure Portal Tasks

3.1. Create new App Registration

  • Launch the Azure portal and navigate to App registrations or click here.

  • Confirm you’re in the Xackley! Studio LLC Directory.

    1. Look in the upper right hand corner.

    2. In the following example, we’re in the DEFAULT DIRECTORY - we don’t want to be here.

      image 2025 06 14 13 34 56 326

    3. Click the persona icon.

    4. Click on the Switch Directory link.

    5. Now click on the Switch button to change to the directory from the currently select one to the Xackley! directory.

      image 2025 06 14 13 42 41 933

    6. Now the persona should indicate the Xackley! Studio LLC Directory

      image 2025 06 14 13 45 22 688

  • Click the + New registration button and provide a name for your application.

  • Provide a Name.

    The Name doesn’t need to match the VS Project name.
  • For the Supported account types select the radio button for:
    Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox) value

  • Provide values for:

    BlazorAuth RegisterApp RedirectURI

    • Select Web as the platform and enter your redirect URIs e.g.:

      https://localhost:5000/signin-oidc
  • Click the Register button.

  • Note the Application (client) ID and Directory (tenant) ID:

    BlazorAuth RegisterApp Essentials

    • These will be required later…​

  • Click on menu item Manage  Authentication

  • Click checkbox for ID tokens (used for implicit and hybrid flows)

  • Click the Save button.

4. Application Updates

4.1. Install required NuGet packages

  • Add the following packages to the Blazor Server project.

    1. Microsoft.AspNetCore.Authentication.OpenIdConnect

    2. Microsoft.Identity.Web

    3. Microsoft.Identity.Web.UI

4.2. Update References

  • Add the following to Components\_Imports.razor:

    @* Added for Microsoft EntraID *@
    @using Microsoft.AspNetCore.Components.Authorization

4.3. Update appsettings.json

  • Add the following to appsettings.json:

      "EntraID": {
        "Instance": "https://login.microsoftonline.com/",
        "Domain": "xackleystudio.onmicrosoft.com",
        "ClientId": "044d74fd-bce1-4917-b208-b4adcda5b80e", (1)
        "TenantId": "abcdefgh-5bd9-4c97-a5f4-7a3a76436359", (2)
        "CallbackPath": "/signin-oidc",
        "Scopes": ""
      }
    1 Replace with the value discovered earlier
    2 Replace with the value discovered earlier

4.4. Update Program.cs

  1. At the top of Program.cs file add:

    // Added for Microsoft EntraID
    using Microsoft.AspNetCore.Authentication.OpenIdConnect;
    using Microsoft.Identity.Web;
    using Microsoft.Identity.Web.UI;
    // End of additions for Microsoft EntraID
  2. After the following in Program.cs:

    // Add services to the container.
    builder.Services.AddRazorComponents()
        .AddInteractiveServerComponents();

    Add the following to Program.cs:

    // Added for Microsoft EntraID
    builder.Services.AddServerSideBlazor()
        .AddMicrosoftIdentityConsentHandler();
    
    builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
      .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("EntraID"));
    
    builder.Services.AddControllersWithViews()
    .AddMicrosoftIdentityUI();
    
    builder.Services.AddAuthorization(options =>
    {
        options.FallbackPolicy = options.DefaultPolicy;
    });
    
    builder.Services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = ForwardedHeaders.XForwardedProto;
        // if you’re in Docker or on an internal network, you may need to clear these
        options.KnownNetworks.Clear();
        options.KnownProxies.Clear();
    });
    // End of additions for Microsoft EntraID
  3. After the following in Program.cs:

    app.UseStaticFiles();
    app.UseAntiforgery();

    Add the following to Program.cs:

    // Added for Microsoft EntraID
    app.UseForwardedHeaders();
    app.UseAuthentication();
    app.UseAuthorization();
    app.MapControllers();
    // End of additions for Microsoft EntraID

4.5. Create/Update Pages

4.5.1. Create Components\Layout\LoginDisplay

  • Create the Components\Layout\LoginDisplay.razor file:

    In the following code, the ability to log in is also accounted for but, it never gets used.

    There doesn’t seem to be a way to initiate the Microsoft Identity log in process; it just happens automatically.

    <AuthorizeView>
    
        <Authorized>
            <!-- Content to display when the user is logged in -->
            Welcome, [@context?.User?.Identity?.Name]!
            <a href ="MicrosoftIdentity/Account/SignOut">LogOut</a>
        </Authorized>
    
        <NotAuthorized>
            <!-- Content to display when the user is not logged in -->
            <h1>Please log in to access this page.</h1>
            <p><a href="MicrosoftIdentity/Account/SignIn">Login</a></p>
        </NotAuthorized>
    
    </AuthorizeView>
    
    @code {
    }

4.5.2. Update Components\Routes.razor

  • Update with the following:

    <CascadingAuthenticationState>
        <Router AppAssembly="typeof(Program).Assembly">
            <Found Context="routeData">
            @* <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
            <FocusOnNavigate RouteData="routeData" Selector="h1" /> *@
    
                <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
                    <NotAuthorized>
                        <p>Sorry, you're not authorized to reach this page.</p>
                        <p>You may need to log in as a different user.</p>
                    </NotAuthorized>
                </AuthorizeRouteView>
            </Found>
            <NotFound>
                <PageTitle>Not found</PageTitle>
                <LayoutView Layout="@typeof(Layout.MainLayout)">
                    <XS_Error404 />
                </LayoutView>
            </NotFound>
        </Router>
    </CascadingAuthenticationState>

4.5.3. Update MainLayout.razor

  • Update Components\Layout\MainLayout.razor

    @inherits LayoutComponentBase
    
    <div class="page">
        <div class="sidebar">
            <NavMenu />
        </div>
    
        <main>
            <div class="top-row px-4">
                <LoginDisplay />
            </div>
    
            <article class="content px-4">
                @Body
            </article>
        </main>
    </div>
    
    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

5. Utilizing Authentication and Authorization in the Blazor App

5.1. Creating and Assigning Roles in Entra ID

This is a two-step process where Roles are first created and then assigned to users.

5.1.1. Create Role(s)

  1. Find the app’s registration from the list.

    Make sure you’re in the Xackley! Studio LLC Directory.
  2. Click on the App.

    If you don’t see the app, you may need to first click on the All applications Tab.
  3. In the left hand menu, click on Manage  App roles menu item.

  4. Click on + Create app role button and create the app role.

    Code will reference the name provided in the Value field of the role and not the Display Name of the role.
    1. Provide a Display name, e.g. Read-Only

    2. Choose the Users/Groups radio button.

    3. Provide a Value that will be referenced in code, e.g., read-only

    4. Provide a Description of this app role.

    5. Click the Apply button.

5.1.2. Assign Role(s)

  1. Find the app’s registration from the list and click on it.

    If you don’t see the app, you may need to first click on the All applications Tab.
  2. Within the Essentials section, click on the Managed application in local directory link which should be pointing to the app name.

  3. Click on the Assign users and groups button.

  4. This will show an existing list of user/app role assignments.

  5. To add an additional user/app role assignment

    1. Click the + Add user/group button.

    2. Under Users Click on None Selected link.

    3. Select the users by clicking the checkbox next to their names and then click the Select button at the bottom.

    4. Under Select a role Click on None Selected link.

    5. Select a role by clicking the name and then click the Select button at the bottom.

    6. Now click the Assign button at the bottom.

  6. To edit an existing user/app role assignment, check the checkbox, and then click the Edit assignment button.

    Do not click on the username!
  7. Finished but…​

    Important:
    Changes to role and assignments do not take effect until the user logs out and then back in!

5.2. Access User Profile Details

Sample code
@code {
[CascadingParameter]
private Task<AuthenticationState>? authenticationState { get; set; }

private string UserName { get; set; } = string.Empty;
private string PreferredUserName { get; set; } = string.Empty;

protected override async Task OnInitializedAsync()
{
    if (authenticationState is not null)
    {
        var state = await authenticationState;

        // Get `name` claim
        UserName = state?.User.Claims
            .Where(c => c.Type.Equals("name"))
            .Select(c => c.Value)
            .FirstOrDefault() ?? string.Empty;

        // Get `preferred_username` claim
        PreferredUserName = state?.User.Claims
            .Where(c => c.Type.Equals("preferred_username"))
            .Select(c => c.Value)
            .FirstOrDefault() ?? string.Empty;
    }
}

5.3. Add Authorization via Claims (Roles)

  • There are two ways to do this:

    1. Test for Claims (Roles):

      Expand for Method 1
      Code to replace home.razor
      @page "/"
      
      <PageTitle>Home</PageTitle>
      
      <h1>Hello, world!</h1>
      
      
      @if (HasAdminRole)
      {
          <p>Has Admin Role!</p>
      }
      else
      {
          <p>Sorry - doesn't have the Admin Role.</p>
      }
      @if (HasReadOnlyRole)
      {
          <p>Has Read-Only Role!</p>
      }
      else
      {
          <p>Sorry - doesn't have the Read-Only Role.</p>
      }
      
      @code {
          [CascadingParameter]
          private Task<AuthenticationState>? authenticationState { get; set; }
      
          private bool HasAdminRole { get; set; } = false;
          private bool HasReadOnlyRole { get; set; } = false;
      
          protected override async Task OnInitializedAsync()
          {
              if (authenticationState is not null)
              {
                  var state = await authenticationState;
      
                  // Test for Roles
                  if (state.User.IsInRole("admin"))
                  {
                      HasAdminRole = true;
                  }
                  if (state.User.IsInRole("read-only"))
                  {
                      HasReadOnlyRole = true;
                  }
              }
          }
      }
    2. This is an alternate method:

      Method 2
      <AuthorizeView Context="YourContext" Roles="role1,role2">   (1) (2)
      
          <Authorized>                                            (3)
              <p>You are Authorized</p>
          </Authorized>
      
          <NotAuthorized>                                         (4)
              <h1>Sorry, but you are not Authorized</h1>
          </NotAuthorized>
      
      </AuthorizeView
      1 The Context tag is only required if some other component is using the default context
      2 Any matching role will satisfy the condition
      3 Code in this block will be executed if the authenticated user satisfies the role condition
      4 Code in this block will be executed if the authenticated user is not in at least one of the listed roles

6. Publishing the App

6.1. Reverse Proxy Setting

These instructions are for Nginx Proxy Manager
This will fix the 502 Bad Gateway error
  1. Launch the Nginx Proxy Manager web gui

  2. Click on the Proxy Hosts button

  3. At the end of the row of the affected proxy host, click on the 3 vertical dots menu to bring up the dropdown menu.

  4. Click on the Edit button

  5. Click on the Advanced button.

  6. Within the Custom Nginx Configuration field, add the following config:

    proxy_busy_buffers_size   512k;
    proxy_buffers   4 512k;
    proxy_buffer_size   256k;
  7. Click the Save button

6.2. Set Forward Headers

For published apps behind a reverse proxy e.g., Nginx Proxy Manager, additional configuration is required in the app’s middleware pipeline.
  1. Open Program.cs

  2. Add the following code afer var app = builder.Build(); but before the app.UseAuthentication(); line:

    Additional code for redirect URI
    /*
        Required to address this issue: Redirect URI specified in the request
        does not match the redirect URIs configured for the application
    */
    app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedProto
    });

6.3. Configure the App Registration’s Redirect URI’s

  1. Navigate to the Azure Portal.

  2. Under Azure services click on Microsoft Entra ID

  3. In the left hand menu, click on Manage  App registrations

  4. Click on All applications tab button to see all existing apps

  5. Click on the app registration

  6. Add Web Redirect URI

    1. Click on the link under the Redirect URI’s heading.

    2. In the Web section, click Add URI

    3. Provide a URI formatted like the following:

      Example
      https://components.xackleystudio.com/signin-oidc
  7. Add Front-channel logout URL

    1. In the Front-channel logout URL section add an entry like the following:

      Example
      https://components.xackleystudio.com/signout-oidc
  8. Click the Save button