{
    "version": "https://jsonfeed.org/version/1",
    "title": "Securing a Blazor Web App",
    "description": "",
    "home_page_url": "https://blazorcode.uk/blazorauthentication",
    "feed_url": "https://blazorcode.uk/blazorauthentication/feed.json",
    "user_comment": "",
    "author": {
        "name": "Christopher J Bell"
    },
    "items": [
        {
            "id": "https://blazorcode.uk/blazorauthentication/sign-in-sign-out-code/",
            "url": "https://blazorcode.uk/blazorauthentication/sign-in-sign-out-code/",
            "title": "Sign in / Sign out code",
            "summary": "This code works! Program.cs using BlazorEntra.Components; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddMicrosoftIdentityConsentHandler();&hellip;",
            "content_html": "\n  <p>\n    This code works!\n  </p>\n\n  <p>\n    Program.cs\n  </p>\n<pre class=\"line-numbers  language-csharp\"><code>using BlazorEntra.Components;\nusing Microsoft.AspNetCore.Authentication.OpenIdConnect;\nusing Microsoft.Identity.Web;\nusing Microsoft.Identity.Web.UI;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Add services to the container.\nbuilder.Services.AddRazorComponents()\n    .AddInteractiveServerComponents()\n    .AddMicrosoftIdentityConsentHandler();          //Added to handle consent and conditional access challenges - 2025-11-20\n\n\nbuilder.Services\n  .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)\n  .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection(\"AzureAd\"))\n  .EnableTokenAcquisitionToCallDownstreamApi() // optional if you'll call APIs\n  .AddInMemoryTokenCaches();\n\nbuilder.Services.Configure&lt;OpenIdConnectOptions&gt;(\n    OpenIdConnectDefaults.AuthenticationScheme,\n    options =&gt;\n    {\n        options.Events.OnRedirectToIdentityProviderForSignOut = context =&gt;\n        {\n            context.ProtocolMessage.PostLogoutRedirectUri =\n                $\"{context.Request.Scheme}://{context.Request.Host}/\";\n            return Task.CompletedTask;\n        };\n    });\n\n\nbuilder.Services.AddAuthorizationBuilder();\n\nbuilder.Services.AddControllersWithViews()\n    .AddMicrosoftIdentityUI();\n\nvar app = builder.Build();\n\n// Configure the HTTP request pipeline.\nif (!app.Environment.IsDevelopment())\n{\n    app.UseExceptionHandler(\"/Error\", createScopeForErrors: true);\n    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n    app.UseHsts();\n}\napp.UseStatusCodePagesWithReExecute(\"/not-found\", createScopeForStatusCodePages: true);\napp.UseHttpsRedirection();\napp.UseStaticFiles();\n\napp.UseAuthentication();\napp.UseAuthorization();\n\napp.UseAntiforgery();\n\napp.MapStaticAssets();\napp.MapRazorComponents&lt;App&gt;()\n    .AddInteractiveServerRenderMode();\n\napp.MapControllers();\n\n\napp.Run();</code></pre>\n\n  <p>\n    App.razor\n  </p>\n<pre class=\"line-numbers  language-csharp\"><code>&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n\n&lt;head&gt;\n    &lt;meta charset=\"utf-8\" /&gt;\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /&gt;\n    &lt;base href=\"/\" /&gt;\n    &lt;ResourcePreloader /&gt;\n    &lt;link rel=\"stylesheet\" href=\"@Assets[\"lib/bootstrap/dist/css/bootstrap.min.css\"]\" /&gt;\n    &lt;link rel=\"stylesheet\" href=\"@Assets[\"app.css\"]\" /&gt;\n    &lt;link rel=\"stylesheet\" href=\"@Assets[\"BlazorEntra.styles.css\"]\" /&gt;\n    &lt;ImportMap /&gt;\n    &lt;link rel=\"icon\" type=\"image/png\" href=\"favicon.png\" /&gt;\n    &lt;HeadOutlet /&gt;\n&lt;/head&gt;\n\n&lt;body&gt;\n    &lt;CascadingAuthenticationState&gt;\n        &lt;Router AppAssembly=\"@typeof(App).Assembly\"&gt;\n            &lt;Found Context=\"routeData\"&gt;\n                &lt;RouteView RouteData=\"@routeData\" DefaultLayout=\"@typeof(MainLayout)\" /&gt;\n                &lt;FocusOnNavigate RouteData=\"@routeData\" Selector=\"h1\" /&gt;\n            &lt;/Found&gt;\n            &lt;NotFound&gt;\n                &lt;PageTitle&gt;Not found&lt;/PageTitle&gt;\n                &lt;LayoutView Layout=\"@typeof(MainLayout)\"&gt;\n                    &lt;p&gt;Sorry, there's nothing at this address.&lt;/p&gt;\n                &lt;/LayoutView&gt;\n            &lt;/NotFound&gt;\n        &lt;/Router&gt;\n    &lt;/CascadingAuthenticationState&gt;\n    &lt;ReconnectModal /&gt;\n    &lt;script src=\"@Assets[\"_framework/blazor.web.js\"]\"&gt;&lt;/script&gt;\n&lt;/body&gt;\n\n&lt;/html&gt;</code></pre>\n\n  <p>\n    MainLayout.razor\n  </p>\n<pre class=\"line-numbers  language-csharp\"><code>@inherits LayoutComponentBase\n@using BlazorEntra.Components.Shared\n@using Microsoft.AspNetCore.Components.Web\n@using Microsoft.Identity.Web.UI\n\n&lt;div class=\"page\"&gt;\n    &lt;div class=\"sidebar\"&gt;\n        &lt;NavMenu /&gt;\n    &lt;/div&gt;\n\n    &lt;main&gt;\n        &lt;div class=\"top-row px-4\"&gt;\n            &lt;MyLoginDisplay /&gt;\n            &lt;a href=\"https://learn.microsoft.com/aspnet/core/\" target=\"_blank\"&gt;About&lt;/a&gt;\n        &lt;/div&gt;\n\n        &lt;article class=\"content px-4\"&gt;\n            @Body\n        &lt;/article&gt;\n    &lt;/main&gt;\n&lt;/div&gt;\n\n&lt;div id=\"blazor-error-ui\" data-nosnippet&gt;\n    An unhandled error has occurred.\n    &lt;a href=\".\" class=\"reload\"&gt;Reload&lt;/a&gt;\n    &lt;span class=\"dismiss\"&gt;🗙&lt;/span&gt;\n&lt;/div&gt;</code></pre>\n\n  <p>\n    MyLoginDisplay.razor\n  </p>\n<pre class=\"line-numbers  language-csharp\"><code>@using Microsoft.AspNetCore.Components.Authorization\n\n&lt;AuthorizeView&gt;\n    &lt;Authorized&gt;\n        @* Hello, @context.User.Identity?.Name! *@\n        Hello, @GetDisplayName(context)!\n\n        &lt;a href=\"MicrosoftIdentity/Account/SignOut\"&gt;Sign out&lt;/a&gt;\n    &lt;/Authorized&gt;\n    &lt;NotAuthorized&gt;\n        &lt;a href=\"MicrosoftIdentity/Account/SignIn\"&gt;Sign in&lt;/a&gt;\n    &lt;/NotAuthorized&gt;\n&lt;/AuthorizeView&gt;\n\n@code {\n    private string GetDisplayName(AuthenticationState? context)\n    {\n        var user = (context?.User);\n        if (user == null) return string.Empty;\n\n        // Try the \"name\" claim first (display name)\n        var displayName = user.FindFirst(\"name\")?.Value;\n\n        // Fallback to email/UPN if \"name\" isn’t present\n        return displayName ?? user.Identity?.Name ?? string.Empty;\n    }\n\n    private void SignIn() =&gt; Nav.NavigateTo(\"MicrosoftIdentity/Account/SignIn\", true);\n    private void SignOut() =&gt; Nav.NavigateTo(\"MicrosoftIdentity/Account/SignOut\", true);\n\n    [Inject] NavigationManager Nav { get; set; } = default!;\n}</code></pre>\n\n  <p>\n    appsettings.json\n  </p>\n<pre class=\"line-numbers  language-json\"><code>{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\",\n\n  \"AzureAd\": {\n    \"Instance\": \"https://BlazorcodeEntra.ciamlogin.com/\",\n    \"Domain\": \"BlazorcodeEntra.onmicrosoft.com\",\n    \"TenantId\": \"3806082c-cff7-4294-8c49-128e4f7bf757\",\n    \"ClientId\": \"fe1803e6-9cce-474f-9a38-4429cf136528\",\n    \"ClientSecret\": \"hb68Q~_unpHABHrtPuhaBjoAiPBBsraWqD~Nfc5F\",\n    \"CallbackPath\": \"/signin-oidc\",\n    \"SignedOutCallbackPath\": \"/signout-callback-oidc\",\n    \"SignedOutRedirectUri\": \"/\",\n    \"RemoteSignOutPath\": \"/signout-oidc\" // optional but good to include\n  }\n}</code></pre>\n\n  <p>\n    Counter.razor\n  </p>\n<pre class=\"line-numbers  language-csharp\"><code>@page \"/counter\"\n@using Microsoft.AspNetCore.Authorization\n@rendermode InteractiveServer\n@attribute [Authorize]\n\n&lt;PageTitle&gt;Counter&lt;/PageTitle&gt;\n\n&lt;h1&gt;Counter&lt;/h1&gt;\n\n&lt;p role=\"status\"&gt;Current count: @currentCount&lt;/p&gt;\n\n&lt;button class=\"btn btn-primary\" @onclick=\"IncrementCount\"&gt;Click me&lt;/button&gt;\n\n@code {\n    private int currentCount = 0;\n\n    private void IncrementCount()\n    {\n        currentCount++;\n    }\n}</code></pre>\n\n  <p>\n    BlazorEntra.csproj\n  </p>\n<pre class=\"line-numbers  language-html\"><code>&lt;Project Sdk=\"Microsoft.NET.Sdk.Web\"&gt;\n\n  &lt;PropertyGroup&gt;\n    &lt;TargetFramework&gt;net10.0&lt;/TargetFramework&gt;\n    &lt;Nullable&gt;enable&lt;/Nullable&gt;\n    &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;\n    &lt;BlazorDisableThrowNavigationException&gt;true&lt;/BlazorDisableThrowNavigationException&gt;\n  &lt;/PropertyGroup&gt;\n\n  &lt;ItemGroup&gt;\n    &lt;PackageReference Include=\"Microsoft.Identity.Web\" Version=\"4.1.0\" /&gt;\n    &lt;PackageReference Include=\"Microsoft.Identity.Web.UI\" Version=\"4.1.0\" /&gt;\n  &lt;/ItemGroup&gt;\n\n&lt;/Project&gt;</code></pre>\n\n  <p>\n    Entra Authorisation\n  </p>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"3\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/6/gallery/Entra-46-EntraAppRegistration-Authentication-01-RedirectURI.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/6/gallery/Entra-46-EntraAppRegistration-Authentication-01-RedirectURI-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Entra Authorisation - Redirect URI</figcaption>\n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/6/gallery/Entra-46-EntraAppRegistration-Authentication-01-Settings.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/6/gallery/Entra-46-EntraAppRegistration-Authentication-01-Settings-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Entra Authorisation - Settings</figcaption>\n    </figure>\n    </div>\n  </div>\n\n  <p>\n    Note that for the Redirect URIs I have not specified a port number for localhost.&nbsp; The same applies 'Front-channel logout URL'. This no longer seems necessary. However, I found that including the port number also worked. It also worked when the Redirect URIs included entries with both localhost + port number and just localhost.\n  </p>",
            "author": {
                "name": "Christopher J Bell"
            },
            "tags": [
            ],
            "date_published": "2025-11-22T11:29:00+00:00",
            "date_modified": "2025-11-23T11:04:09+00:00"
        },
        {
            "id": "https://blazorcode.uk/blazorauthentication/enhancing-blazor-web-app/",
            "url": "https://blazorcode.uk/blazorauthentication/enhancing-blazor-web-app/",
            "title": "Enhancing Blazor Web App",
            "summary": "Introduction So far we have our Blazor web app linked to Entra External ID and have authentication/authorisation on the Counter page, forcing a user to&hellip;",
            "content_html": "\n    <h2 id=\"introduction\">\n      Introduction\n    </h2>\n\n  <p>\n    So far we have our Blazor web app linked to Entra External ID and have authentication/authorisation on the Counter page, forcing a user to log in before being given access.&nbsp; However, once logged in there is no method to allow the user to log out and other than selecting the Counter page there is no other way to log in.\n  </p>\n\n  <p>\n    Here we will add specific <em>Sign in</em> and <em>Sign out</em> options\n  </p>\n\n  <p>\n    The changes we need to make are:\n  </p>\n\n    <h2 id=\"_importsrazor\">\n      _Imports.razor\n    </h2>\n\n  <p>\n    Add the following to _Imports. This allows us to use authorization attributes and identity UI helpers anywhere in our components. It ensures that <code>[Authorise]</code> works in all .razor pages and we can drop the built-in login/logout UI.\n  </p>\n<pre class=\"line-numbers  language-csharp\"><code>@using Microsoft.AspNetCore.Authorization\n@using Microsoft.AspNetCore.Components.Authorization\n@using Microsoft.Identity.Web\n@using Microsoft.Identity.Web.UI</code></pre>\n\n  <p>\n    The full _Imports.razor should now be:\n  </p>\n<pre class=\"line-numbers  language-csharp\"><code>@using System.Net.Http\n@using System.Net.Http.Json\n@using Microsoft.AspNetCore.Components.Forms\n@using Microsoft.AspNetCore.Components.Routing\n@using Microsoft.AspNetCore.Components.Web\n@using static Microsoft.AspNetCore.Components.Web.RenderMode\n@using Microsoft.AspNetCore.Components.Web.Virtualization\n@using Microsoft.JSInterop\n@using BlazorEntra\n@using BlazorEntra.Components\n@using BlazorEntra.Components.Layout\n@using Microsoft.AspNetCore.Authorization\n@using Microsoft.AspNetCore.Components.Authorization\n@using Microsoft.Identity.Web\n@using Microsoft.Identity.Web.UI</code></pre>\n\n    <h2 id=\"programcs\">\n      Program.cs\n    </h2>\n\n  <p>\n    We need to make some change to Program.cs, and rather than do this piecemeal, the new version of Program.cs is shown below. I have attempted to structure the code so that it is grouped by concern (Blazor services, authentication/token services, OIDC options, authorisation/UI), chained related service registrations for readability with clear separation between Services and Middleware pipeline.&nbsp;\n  </p>\n<pre class=\"line-numbers  language-csharp\"><code>using BlazorEntra.Components;\nusing Microsoft.AspNetCore.Authentication.OpenIdConnect;\nusing Microsoft.Identity.Web;\nusing Microsoft.Identity.Web.UI;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// =======================================\n// Services\n// =======================================\n\n// Blazor + UI services\nbuilder.Services\n    .AddRazorComponents()\n        .AddInteractiveServerComponents()\n        .AddMicrosoftIdentityConsentHandler(); // handles consent & conditional access\n\n// Authentication + Token services\nbuilder.Services\n    .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)\n        .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection(\"AzureAd\"))\n        .EnableTokenAcquisitionToCallDownstreamApi() // optional if you call APIs\n        .AddInMemoryTokenCaches();\n\n// Configure OpenID Connect options (logout redirect)\nbuilder.Services.Configure&lt;OpenIdConnectOptions&gt;(\n    OpenIdConnectDefaults.AuthenticationScheme,\n    options =&gt;\n    {\n        options.Events.OnRedirectToIdentityProviderForSignOut = context =&gt;\n        {\n            context.ProtocolMessage.PostLogoutRedirectUri =\n                $\"{context.Request.Scheme}://{context.Request.Host}/\";\n            return Task.CompletedTask;\n        };\n    });\n\n// Authorization + Identity UI\nbuilder.Services\n    .AddAuthorization();\nbuilder.Services\n    .AddControllersWithViews()\n    .AddMicrosoftIdentityUI();\n\nvar app = builder.Build();\n\n// =======================================\n// Middleware pipeline\n// =======================================\n\nif (!app.Environment.IsDevelopment())\n{\n    app.UseExceptionHandler(\"/Error\", createScopeForErrors: true);\n    app.UseHsts();\n}\n\napp.UseStatusCodePagesWithReExecute(\"/not-found\", createScopeForStatusCodePages: true);\napp.UseHttpsRedirection();\napp.UseStaticFiles();\n\napp.UseAuthentication();\napp.UseAuthorization();\napp.UseAntiforgery();\n\napp.MapStaticAssets();\napp.MapRazorComponents&lt;App&gt;()\n    .AddInteractiveServerRenderMode();\n\napp.MapControllers();\n\napp.Run();</code></pre>\n\n    <h2 id=\"appsettingsjson\">\n      appsettings.json\n    </h2>\n\n  <p>\n    To handle logging out we need to add three lines to appsetings.json as shown below:\n  </p>\n<pre class=\"line-numbers  language-json\"><code>  {\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\",\n\n  \"AzureAd\": {\n    \"Instance\": \"https://{your-tenant}.ciamlogin.com/\",\n    \"Domain\": \"{your-tenant}.onmicrosoft.com\",\n    \"TenantId\": \"{GUID-of-external-tenant}\",\n    \"ClientId\": \"{APP-REGISTRATION-CLIENT-ID}\",\n    \"ClientSecret\": \"{APP-REGISTRATION-CLIENT-SECRET-VALUE}\",\n    \"CallbackPath\": \"/signin-oidc\",   \n    \"SignedOutCallbackPath\": \"/signout-callback-oidc\",\n    \"SignedOutRedirectUri\": \"/\",\n    \"RemoteSignOutPath\": \"/signout-oidc\" // optional but good to include\n  }\n}\n</code></pre>\n\n    <h2 id=\"apprazor\">\n      App.razor\n    </h2>\n\n  <p>\n    This is the code we need for App.razor. The significant change is the addition of the <code>CascadingAuthenticationState</code> section.\n  </p>\n<pre class=\"line-numbers  language-csharp\"><code>&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n\n&lt;head&gt;\n    &lt;meta charset=\"utf-8\" /&gt;\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /&gt;\n    &lt;base href=\"/\" /&gt;\n    &lt;ResourcePreloader /&gt;\n    &lt;link rel=\"stylesheet\" href=\"@Assets[\"lib/bootstrap/dist/css/bootstrap.min.css\"]\" /&gt;\n    &lt;link rel=\"stylesheet\" href=\"@Assets[\"app.css\"]\" /&gt;\n    &lt;link rel=\"stylesheet\" href=\"@Assets[\"BlazorEntra.styles.css\"]\" /&gt;\n    &lt;ImportMap /&gt;\n    &lt;link rel=\"icon\" type=\"image/png\" href=\"favicon.png\" /&gt;\n    &lt;HeadOutlet /&gt;\n&lt;/head&gt;\n\n&lt;body&gt;\n    &lt;CascadingAuthenticationState&gt;\n        &lt;Router AppAssembly=\"@typeof(App).Assembly\"&gt;\n            &lt;Found Context=\"routeData\"&gt;\n                &lt;RouteView RouteData=\"@routeData\" DefaultLayout=\"@typeof(MainLayout)\" /&gt;\n                &lt;FocusOnNavigate RouteData=\"@routeData\" Selector=\"h1\" /&gt;\n            &lt;/Found&gt;\n            &lt;NotFound&gt;\n                &lt;PageTitle&gt;Not found&lt;/PageTitle&gt;\n                &lt;LayoutView Layout=\"@typeof(MainLayout)\"&gt;\n                    &lt;p&gt;Sorry, there's nothing at this address.&lt;/p&gt;\n                &lt;/LayoutView&gt;\n            &lt;/NotFound&gt;\n        &lt;/Router&gt;\n    &lt;/CascadingAuthenticationState&gt;\n    &lt;ReconnectModal /&gt;\n    &lt;script src=\"@Assets[\"_framework/blazor.web.js\"]\"&gt;&lt;/script&gt;\n&lt;/body&gt;\n\n&lt;/html&gt;</code></pre>\n\n  <p>\n    The key elements of the above are:\n  </p>\n\n  <ul>\n    <li><code>&lt;CascadingAuthenticationState&gt;</code></li><ul><li>Wraps the entire app in an authentication context</li><li>Provides the current user's identity and claims to components like <code>&lt;AuthoriseView&gt;</code> and <code>&lt;LoginDisplay&gt;</code></li><li>Without this, Blazor components wouldn't know if a user was signed in.</li></ul><li><span style=\"font-size: 1em;\"><code>&lt;RouterAssembly=\"@typeof(App).Assembly\"&gt;</code></span></li><ul><li>Handles navigation between pages (@page components)</li><li>Looks inside the app assembly for routable components</li><li>Decides what to render based on the current URL</li></ul><li><span style=\"font-size: 1em;\"><code>&lt;Found Context = \"routeData\"&gt;</code></span></li><ul><li>Executes when the router finds a matching page</li><li><code>&lt;RouteView&gt;</code>: Renders the matched component, wrapped in MainLayout</li><li><code>&lt;FocusOnNavigate&gt;</code> Moves the keyboard focus to the first <code>&lt;h1&gt;</code> on the page after navigation (it's an accessibility feature)</li></ul><li><span style=\"font-size: 1em;\"><code>&lt;NotFound&gt;</code></span></li><ul><li>Executed when no route matches the URL</li><li>Sets the page title to 'Not Found'</li><li>Renders a fallback layout (MainLayout) with a simple error message</li></ul><li><span style=\"font-size: 1em;\"><code>&lt;ReconnectModal/&gt;</code></span></li><ul><li>A built-in Blazor component that appears if the Signal-R connection between the server and the client drops</li><li>Lets the user reconnect without refreshing the whole page</li></ul><li><span style=\"font-size: 1em;\"><code>&lt;script src=\"@Assets[\"_framework/blazor.web.js\"]\"&gt;&lt;script/&gt;</code></span></li><ul><li>Loads the Blazor runtime JavaScript</li></ul>\n  </ul>\n\n  <p>\n    In short, App.razor wires up authentication, routing, layouts, accessibility, error handling and connection resilience - it's the \"engine room\" of the Blazor application.\n  </p>\n\n  <p>\n    \n  </p>\n\n  <p>\n    \n  </p>\n\n    <h2 id=\"mainlayoutrazor\">\n      MainLayout.razor\n    </h2>\n<pre class=\"line-numbers  language-csharp\"><code>@inherits LayoutComponentBase\n@using BlazorEntra.Components.Shared\n@using Microsoft.Identity.Web.UI\n\n&lt;div class=\"page\"&gt;\n    &lt;div class=\"sidebar\"&gt;\n        &lt;NavMenu /&gt;\n    &lt;/div&gt;\n\n    &lt;main&gt;\n        &lt;div class=\"top-row px-4\"&gt;\n            &lt;MyLoginDisplay /&gt;\n            &lt;a href=\"https://learn.microsoft.com/aspnet/core/\" target=\"_blank\"&gt;About&lt;/a&gt;\n        &lt;/div&gt;\n\n        &lt;article class=\"content px-4\"&gt;\n            @Body\n        &lt;/article&gt;\n    &lt;/main&gt;\n&lt;/div&gt;\n\n&lt;div id=\"blazor-error-ui\" data-nosnippet&gt;\n    An unhandled error has occurred.\n    &lt;a href=\".\" class=\"reload\"&gt;Reload&lt;/a&gt;\n    &lt;span class=\"dismiss\"&gt;🗙&lt;/span&gt;\n&lt;/div&gt;</code></pre>\n\n  <p>\n    The significant change to MainLayout.razor is the inclusion of the &lt;MyLoginDisplay&gt; component and the <code>@using</code> statements at the top of the file.\n  </p>\n\n  <p>\n    I have specifically called the 'login display' component 'MyLoginDisplay' component because I believe older Blazor templates automatically included a 'LoginDisplay' component and I didn't want any possible confusion.&nbsp; We will come to MyLoginDisplay' shortly, but it will be saved in a new sub-folder of Components called 'Shared', hence the inclusion of this folder in the using statements.\n  </p>\n\n  <p>\n    Adding&nbsp; the &lt;MyLoginDisplay&gt; component will insert a 'Sign in' link to the top bar.\n  </p>\n\n  <p>\n    \n  </p>\n\n    <h2 id=\"mylogindisplayrazor\">\n      MyLoginDisplay.razor\n    </h2>\n\n  <ul>\n    <li>Create a sub-folder of Components and call it 'Shared'</li><li>Add a new razor file called 'MyLoginDisplay'</li><li>Insert the following code</li>\n  </ul>\n<pre class=\"line-numbers  language-csharp\"><code>&lt;AuthorizeView&gt;\n    &lt;Authorized&gt;\n        Hello, @context.User.Identity?.Name!\n        &lt;a href=\"MicrosoftIdentity/Account/SignOut\"&gt;Sign out&lt;/a&gt;\n    &lt;/Authorized&gt;\n    &lt;NotAuthorized&gt;\n        &lt;a href=\"MicrosoftIdentity/Account/SignIn\"&gt;Sign in&lt;/a&gt;\n    &lt;/NotAuthorized&gt;\n&lt;/AuthorizeView&gt;\n</code></pre>\n\n  <p>\n    This is the minimum that's required, but we will enhance this later.\n  </p>\n\n    <h2 id=\"entra-external-id\">\n      Entra External ID\n    </h2>\n\n  <p>\n    We need to make a change to Entra External ID to ensure that sign-outs are handled correctly; this is to add a 'Front-channel logout URL'.\n  </p>\n\n  <p>\n    The full instructions are:\n  </p>\n\n  <ul>\n    <li>Go to <a href=\"https://entra.microsoft.com\">https://entra.microsoft.com</a> and sign in.</li><li>Switch to your Entra ID tenant</li><li>Select <strong>Entra ID</strong> &gt; <strong>App registrations</strong> &gt; <strong>All applications</strong></li><li>Select the relevant application and click to open it</li><li>Select<strong> Authentication</strong> &gt; <strong>Settings</strong></li><li>In <strong>Front-channel logout URL</strong> enter:</li><ul><li><code>https://localhost/signout-callback-oidc</code></li></ul><li>Click <strong>Save</strong></li>\n  </ul>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"2\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-47.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-47-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-48.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-48-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n    <h2 id=\"save-andamp-test\">\n      Save &amp; Test\n    </h2>\n\n  <p>\n    Return to Visual Studio, save all the files and test.\n  </p>\n\n  <p>\n    You should be able to sign into the application, open the Counter page and sign out. Sign out should return you to the application, enabling you to sign in again if you wish.\n  </p>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"5\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-49.png\" data-size=\"1425x908\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-49-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-50.png\" data-size=\"1425x908\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-50-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-51.png\" data-size=\"1425x908\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-51-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-52.png\" data-size=\"1425x908\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-52-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-53.png\" data-size=\"1425x908\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-53-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n    <h2 id=\"one-last-thing\">\n      One Last Thing\n    </h2>\n\n  <p>\n    You will notice that although the code in MyLoginDisplay.razor implied that the greeting at the top of the screen would welcome the user by name it actually displays the user's email address.\n  </p>\n\n  <p>\n    When we created the user flow in Entra we had a choice of which 'User attributes' we wanted to collect from the user when they registered. Our choice was to keep it simple and just collect the 'Display name'. (We could have collected the user's 'Given name', 'Surname', and/or address elements, etc.) I think it would be better to greet the user by name rather than email address.\n  </p>\n\n  <p>\n    To do this we need to alter MyDisplayLogin.razor to:\n  </p>\n<pre class=\"line-numbers  language-csharp\"><code>&lt;AuthorizeView&gt;\n    &lt;Authorized&gt;\n        @* Hello, @context.User.Identity?.Name! *@\n        Hello, @GetDisplayName(context)!\n\n        &lt;a href=\"MicrosoftIdentity/Account/SignOut\"&gt;Sign out&lt;/a&gt;\n    &lt;/Authorized&gt;\n    &lt;NotAuthorized&gt;\n        &lt;a href=\"MicrosoftIdentity/Account/SignIn\"&gt;Sign in&lt;/a&gt;\n    &lt;/NotAuthorized&gt;\n&lt;/AuthorizeView&gt;\n\n@code {\n    private string GetDisplayName(AuthenticationState? context)\n    {\n        var user = (context?.User);\n        if (user == null) return string.Empty;\n\n        // Try the \"name\" claim first (display name)\n        var displayName = user.FindFirst(\"name\")?.Value;\n\n        // Fallback to email/UPN if \"name\" isn’t present\n        return displayName ?? user.Identity?.Name ?? string.Empty;\n    }\n\n    private void SignIn() =&gt; Nav.NavigateTo(\"MicrosoftIdentity/Account/SignIn\", true);\n    private void SignOut() =&gt; Nav.NavigateTo(\"MicrosoftIdentity/Account/SignOut\", true);\n\n    [Inject] NavigationManager Nav { get; set; } = default!;\n}\n</code></pre>\n\n  <p>\n    This code attempts to get the \"name\" claim from the signed in user (confusingly called 'name', whereas the Entra ID user flow calls id 'Display name') and assigns it to the variable 'displayName' (somewhat ironically!), otherwise it returns the 'name', i.e. email address.\n  </p>\n\n  <p>\n    The little block starting with 'private void...' is there to make the <em>sign in</em> and <em>sign out</em> links work correctly in Blazor Server.&nbsp;&nbsp;That code injects NavigationManager so the component can programmatically navigate to the Entra sign in/out endpoints, forcing a full reload. It prevents Blazor’s router from swallowing the navigation and ensures the authentication flow works reliably.\n  </p>\n\n  <p>\n    If we re-run the application it will now display:\n  </p>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"1\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-54.png\" data-size=\"1427x908\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/5/gallery/Entra-54-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>",
            "author": {
                "name": "Christopher J Bell"
            },
            "tags": [
            ],
            "date_published": "2025-11-20T12:32:09+00:00",
            "date_modified": "2025-11-28T09:59:52+00:00"
        },
        {
            "id": "https://blazorcode.uk/blazorauthentication/blazor-server-web-app/",
            "url": "https://blazorcode.uk/blazorauthentication/blazor-server-web-app/",
            "title": "Blazor Server Web App",
            "summary": "Creating a new Blazor Web App Open Visual Studio (I'm using Visual Studio 2026 - Community Edition) These can be added either by using the&hellip;",
            "content_html": "\n    <h2 id=\"creating-a-new-blazor-web-app\">\n      Creating a new Blazor Web App\n    </h2>\n\n  <p>\n    Open Visual Studio (I'm using Visual Studio 2026 - Community Edition)\n  </p>\n\n  <ul>\n    <li>Select <strong>Create a new project</strong></li><li>Enter Blazor in the template search box (if necessary)</li><li>Select <strong>Blazor Web App</strong></li><li>On the <strong>Configure your new project</strong>&nbsp;form enter</li><ul><li>A suitable <strong>Project name</strong></li><li>Leave the <strong>Location </strong>as the default</li><li>Leave the<strong> Solution name</strong> the same as the Project name</li><li>Click Next</li></ul><li>On the <strong>Additional information</strong> form</li><ul><li>Select <strong>.NET 10.0</strong></li><li>Leave <strong>Authentication</strong> as <strong>None</strong></li><li>Select <strong>Server</strong> for <strong>Interactive render mod</strong>e</li><li>Select <strong>Per page/component</strong> for <strong>Interactivity location</strong></li><li>Leave <strong>Include sample pages</strong> as checked</li><li>Click<strong> Create</strong></li></ul>\n  </ul>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"4\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-31.png\" data-size=\"890x688\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-31-thumbnail.png\" height=\"594\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-16.png\" data-size=\"890x688\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-16-thumbnail.png\" height=\"594\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-17.png\" data-size=\"890x688\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-17-thumbnail.png\" height=\"594\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-18.png\" data-size=\"890x688\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-18-thumbnail.png\" height=\"594\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n    <h2 id=\"adding-microsoft-identity-to-a-blazor-server-app\">\n      Adding Microsoft Identity to a Blazor Server App\n    </h2>\n\n    <h3 id=\"add-nuget-packages\">\n      Add NuGet packages\n    </h3>\n\n  <ul>\n    <li>Microsoft.Identity.Web</li><li>Microsoft.Identity.Web.UI</li>\n  </ul>\n\n  <p>\n    These can be added either by using the CLI (Command Line Interface - e.g. NuGet Package Manager Console, Bash or Powershell making sure you are in the solution folder.) These are the commands:\n  </p>\n<pre class=\"line-numbers  language-powershell\"><code>dotnet add package Microsoft.Identity.Web\ndotnet add package Microsoft.Identity.Web.UI</code></pre>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"1\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-36.png\" data-size=\"1600x1018\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-36-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n  <p>\n    In the above example I used the&nbsp;NuGet Package Manager Console, but note that I had to change directory to ..\\repos\\BlazorEntra\\BlazorEntra\n  </p>\n\n  <p>\n    Alternatively, use the NuGet Package Manager within Visual Studio.\n  </p>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"4\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-32.png\" data-size=\"1600x1018\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-32-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-33.png\" data-size=\"1600x1018\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-33-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-34.png\" data-size=\"1600x1018\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-34-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-35.png\" data-size=\"1600x1018\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-35-thumbnail.png\" height=\"489\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n    <h3 id=\"configure-appsettingsjson\">\n      Configure appsettings.json\n    </h3>\n\n  <p>\n    I will not be uploading this code to GitHub, so for simplicity will be making changes directly in appsettings.json. Insert the following placeholder code in sppsettings.json.\n  </p>\n<pre class=\"line-numbers  language-json\"><code>  \"AzureAd\": {\n    \"Instance\": \"https://{your-tenant}.ciamlogin.com/\",\n    \"Domain\": \"{your-tenant}.onmicrosoft.com\",\n    \"TenantId\": \"{GUID-of-external-tenant}\",\n    \"ClientId\": \"{APP-REGISTRATION-CLIENT-ID}\",\n    \"ClientSecret\": \"{APP-REGISTRATION-CLIENT-SECRET-VALUE}\",\n    \"CallbackPath\": \"/signin-oidc\"\n  }</code></pre>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"1\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-37-2.png\" data-size=\"788x400\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-37-2-thumbnail.png\" height=\"390\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n  <p>\n    We now need to replace the placeholders with data from Entra ID.\n  </p>\n\n  <p>\n    Replace {your-tenant} with the name of your Entra ID tenant. To find the {tenant-name} open the Azure portal switch to the Entra ID tenant.&nbsp;\n  </p>\n\n  <ul>\n    <li>Select <strong>Entra ID &gt; Overview</strong> from the left-hand menu and then <strong>Overview</strong></li><li><font color=\"#02192b\"><strong>{your-tenant}</strong> is the part of the<strong> Primary domain</strong> before <strong>.onmicrosoft.com</strong>. (Not the Name of the tenant)</font></li><li><strong>{GUID-of-external-tenant}</strong> is <strong>Tenant ID</strong></li><li>To find the <strong>{APP-REGISTRATION_CLIENT_ID}</strong> select <strong>Entra ID &gt; App registrations &gt; All applications.</strong>&nbsp;The <strong>Application (client) ID</strong> is displayed in the column with the same name.</li><li>To find the<strong>&nbsp;{APP-REGISTRATION-CLIENT-SECRET-VALUE}</strong>&nbsp;select <strong>Entra ID &gt;</strong> <strong>App registrations &gt; All applications &gt; Certificates and secrets</strong> and copy the <strong>Value</strong> of the relevant secret (I imagine any secret would do if you created more than one).</li>\n  </ul>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"2\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-38-2.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-38-2-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-39.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-39-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n    <h3 id=\"amend-programcs-to-use-authentication\">\n      Amend Program.cs to use Authentication\n    </h3>\n\n    <h3 id=\"programcs\">\n      Program.cs\n    </h3>\n\n  <ul>\n    <li>In Program.cs add the following using statements at the top of the file:</li>\n  </ul>\n<pre class=\"line-numbers  language-csharp\"><code>using Microsoft.AspNetCore.Authentication.OpenIdConnect;\nusing Microsoft.Identity.Web;</code></pre>\n\n  <ul>\n    <li>In the <strong>builder</strong> section insert the following:</li>\n  </ul>\n<pre class=\"line-numbers  language-csharp\"><code>  builder.Services\n  .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)\n  .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection(\"AzureAd\"))\n    .EnableTokenAcquisitionToCallDownstreamApi() // optional if you'll call APIs\n    .AddInMemoryTokenCaches();\n</code></pre>\n\n  <ul>\n    <li>And lower in the file insert:</li>\n  </ul>\n<pre class=\"line-numbers  language-csharp\"><code>app.UseAuthentication();\napp.UseAuthorization();</code></pre>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"1\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-45.png\" data-size=\"1154x762\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/4/gallery/Entra-45-thumbnail.png\" height=\"507\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n    <h3 id=\"countercs\">\n      Counter.cs\n    </h3>\n\n  <p>\n    We'll use the Counter page to illustrate the amendments needed to require a user to log in to access the page.\n  </p>\n\n  <ul>\n    <li>Open Counter.cs and add the following to the top of the file:</li>\n  </ul>\n<pre class=\"line-numbers  language-csharp\"><code>@using Microsoft.AspNetCore.Authorization\n@attribute [Authorize]\n</code></pre>\n\n  <p>\n    That's it! That's all that's needed to add authentication to the Counter page.\n  </p>\n\n    <h2 id=\"save-andamp-test\">\n      Save &amp; Test\n    </h2>\n\n  <p>\n    Save all files and run the application. The application should run and display the 'standard' Blazor application. However, as we have added authorisation to the Counter page, clicking on the&nbsp;\n  </p>\n\n    <h2 id=\"code\">\n      Code\n    </h2>\n\n  <p>\n    The full code for the files changed is:\n  </p>\n\n    <h4 id=\"appsettingsjson\">\n      appsettings.json\n    </h4>\n<pre class=\"line-numbers  language-json\"><code>{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\",\n\n  \"AzureAd\": {\n    \"Instance\": \"https://{your-tenant}.ciamlogin.com/\",\n    \"Domain\": \"{your-tenant}.onmicrosoft.com\",\n    \"TenantId\": \"{GUID-of-external-tenant}\",\n    \"ClientId\": \"{APP-REGISTRATION-CLIENT-ID}\",\n    \"ClientSecret\": \"{APP-REGISTRATION-CLIENT-SECRET-VALUE}\",\n    \"CallbackPath\": \"/signin-oidc\"\n  }\n}</code></pre>\n\n    <h4 id=\"programcs\">\n      Program.cs\n    </h4>\n<pre class=\"line-numbers  language-csharp\"><code>using BlazorEntra.Components;\nusing Microsoft.AspNetCore.Authentication.OpenIdConnect;\nusing Microsoft.Identity.Web;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Add services to the container.\nbuilder.Services.AddRazorComponents()\n    .AddInteractiveServerComponents();\n\nbuilder.Services\n  .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)\n  .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection(\"AzureAd\"))\n    .EnableTokenAcquisitionToCallDownstreamApi() // optional if you'll call APIs\n    .AddInMemoryTokenCaches();\n\nvar app = builder.Build();\n\n// Configure the HTTP request pipeline.\nif (!app.Environment.IsDevelopment())\n{\n    app.UseExceptionHandler(\"/Error\", createScopeForErrors: true);\n    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n    app.UseHsts();\n}\napp.UseStatusCodePagesWithReExecute(\"/not-found\", createScopeForStatusCodePages: true);\napp.UseHttpsRedirection();\napp.UseStaticFiles();\n\napp.UseAuthentication();\napp.UseAuthorization();\n\napp.UseAntiforgery();\n\napp.MapStaticAssets();\napp.MapRazorComponents&lt;App&gt;()\n    .AddInteractiveServerRenderMode();\n\napp.Run();</code></pre>\n\n    <h3 id=\"counterrazor\">\n      Counter.razor\n    </h3>\n<pre class=\"line-numbers  language-csharp\"><code>@page \"/counter\"\n@using Microsoft.AspNetCore.Authorization\n@rendermode InteractiveServer\n@attribute [Authorize]\n\n&lt;PageTitle&gt;Counter&lt;/PageTitle&gt;\n\n&lt;h1&gt;Counter&lt;/h1&gt;\n\n&lt;p role=\"status\"&gt;Current count: @currentCount&lt;/p&gt;\n\n&lt;button class=\"btn btn-primary\" @onclick=\"IncrementCount\"&gt;Click me&lt;/button&gt;\n\n@code {\n    private int currentCount = 0;\n\n    private void IncrementCount()\n    {\n        currentCount++;\n    }\n}</code></pre>",
            "author": {
                "name": "Christopher J Bell"
            },
            "tags": [
            ],
            "date_published": "2025-11-16T11:21:09+00:00",
            "date_modified": "2025-11-23T11:59:11+00:00"
        },
        {
            "id": "https://blazorcode.uk/blazorauthentication/entra-getting-started/",
            "url": "https://blazorcode.uk/blazorauthentication/entra-getting-started/",
            "title": "Entra - Getting Started",
            "summary": "Creating an Entra Tenant Add a new tenant This assumes you already have an Azure subscription; if you don't have a subscription you can sign&hellip;",
            "content_html": "\n    <h2 id=\"creating-an-entra-tenant\">\n      Creating an Entra Tenant\n    </h2>\n\n    <h3 id=\"add-a-new-tenant\">\n      Add a new tenant\n    </h3>\n\n  <p>\n    This assumes you already have an Azure subscription; if you don't have a subscription you can sign up for a free trial here:&nbsp;<a href=\"https://azure.microsoft.com/en-us/pricing/purchase-options/azure-account\" target=\"_blank\">https://azure.microsoft.com/en-us/pricing/purchase-options/azure-account</a>. Once the free trial has expired you can convert the account to 'Pay as you go', although there is no charge as far as we are concerned, with regards to Entra External ID so long as the Monthly User Accesses remain under 50,000.\n  </p>\n\n  <ul>\n    <li>Go to<a href=\"https://entra.microsoft.com\" target=\"_blank\"> https://entra.microsoft.com</a>&nbsp;and sign in</li><li>On the left-hand menu, under Entra ID, select 'Overview' (slide 1)</li><li>Select 'Manage Tenants' (slide 2)</li><li>Select 'Create' (slide 3)</li><li>Select 'External' and click 'Continue' (slide 4)</li><li>Select 'Use Azure subscription' (slide 5)</li><li>Enter the following: (slide 6)</li><ul><li>Tenant Name - this is an internal name for the tenant - I don't believe it has to be unique, but it makes sense to name it so you can identify it)</li><li>Domain Name - this must be unique (this the URL that our application will access to get a token to authenticate)</li><li>Select a suitable location</li></ul><li><span style=\"font-size: 1em;\">Click 'Next - Add a subscription' (slide 7)</span></li><ul><li>Select a subscription from the drop-down list</li><li>For 'Resource group' I suggest selecting 'Create new' as this will allow deletion of all items within the resource group later if one wants to clean up everything associated with this investigation.</li><li>Select a 'Resource group location'</li></ul><li><span style=\"font-size: 1em;\">Click 'Review + create' (slide 8)</span></li><li><span style=\"font-size: 1em;\">Click 'Create' to complete the addition of the Entra External ID tenant. (This may take a few minutes.) (slide 9)</span></li>\n  </ul>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"3\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-01.jpg\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-01-thumbnail.jpg\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Slide 1 - Entra ID home page</figcaption>\n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-02.jpg\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-02-thumbnail.jpg\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Slide 2 - Overview</figcaption>\n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-03.jpg\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-03-thumbnail.jpg\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Slide 3 - Manage tenants</figcaption>\n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-04.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-04-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Slide 4 - Create tenant - External</figcaption>\n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-05.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-05-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Slide 5 - Create tenant - Add Azure subscription</figcaption>\n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-06.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-06-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Slide 6 - Create tenant - Tenant name, Domain name & Country/Region</figcaption>\n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-07.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-07-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Slide 7 - Create tenant - Add subscription and Resource group</figcaption>\n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-08.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-08-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Slide 8 - Create tenant - Review + create</figcaption>\n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-09.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-09-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      <figcaption>Slide 9 - Create tenant - complete</figcaption>\n    </figure>\n    </div>\n  </div>\n\n  <p>\n    That will create the Entra tenant and give you the option to switch to it. If you select to switch to the new tenant you will be forced to enable Multifactor Authentication (see next section), otherwise, to see the new tenant listed, select 'Overview' under 'Entra ID' in the left-hand menu followed by 'Manage tenants'. The new tenant will be shown with the 'Tenant type' of 'External'.\n  </p>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"1\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-09a.jpg\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-09a-thumbnail.jpg\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n  <p>\n    As an aside, there are 5 tenants shown in the above, the one we have just created plus two of tenant type 'Workforce' and two of type 'Azure AD B2C'. The two 'Workforce' are defaults associated with my two Azure subscriptions, one as a result of my 'Microsoft Outlook' account (i.e. personal account) and the other my workplace account (Microsoft 365 Business, linked with blazorcode.uk). The 'Azure AD B2C' tenants are two tenants I created some time ago and for unknown reasons I cannot delete!\n  </p>\n\n    <h3 id=\"andnbspswitch-to-the-new-tenant\">\n      &nbsp;Switch to the new tenant\n    </h3>\n\n  <p>\n    The first time you switch to the new tenant you are forced to enable Multifactor Authentication (MFA). This is simply a matter of stepping through the prompts.&nbsp; If you don't already have the Microsoft Authenticator app on your phone you will need to download it, otherwise just follow the prompts.\n  </p>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"3\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-10.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-10-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-11.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-11-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-12.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-12-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-13.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-13-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-14.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-14-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-15.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-15-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n  <p>\n    As an aside I have had terrible frustrations with Azure tenants MFA. From one of the screenshots above you will notice that I have 2 old Azure B2C tenants - I now seem to be totally locked out of these.\n  </p>\n\n    <h2 id=\"create-sign-upsign-in-user-flow\">\n      Create Sign-up/Sign-in User Flow\n    </h2>\n\n  <p>\n    It might seem that we are jumping the gun here by creating a user flow before we have created the Blazor web app, but at this stage we don't need any information about the web app.\n  </p>\n\n  <ul>\n    <li>If you haven't already, switch to the new tenant. Use the <strong>Settings</strong> icon  in the top menu to switch to your external tenant from the Directories + subscriptions menu.</li><li>Browse to <strong>Entra ID &gt; External Identities &gt; User flows</strong></li><li>Select <strong>New user flow</strong></li><li>On the <strong>Create</strong> page, enter a <strong>Name</strong> for the user flow (for example, \"SignUpSignIn\").</li><li>Select either 'Email with password' or 'Email with one-time passcode'.</li><li>In<strong> User Attributes</strong> select the values you want to collect when the user signs up. I suggest keeping it very simple, but if you want collect additional information, such as address details, click 'Show more...'</li>\n  </ul>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"5\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-19.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-19-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-20.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-20-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-21.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-21-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-22.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-22-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-23.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-23-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n    <h2 id=\"register-the-blazor-server-app\">\n      Register the Blazor Server app\n    </h2>\n\n  <ul>\n    <li>Browse to <strong>Entra ID &gt; App registrations &gt; New registration</strong></li><li>Enter a <strong>Name</strong> for the application (just to identify it - in this example BlazorEntraApp)</li><li>Select <strong>Accounts in this organizational directory only</strong> (first option)</li><li>In <strong>Redirect URI</strong> select Web and enter:&nbsp;<strong>https://localhost/signin-oidc</strong> (plus, maybe the port number?)</li><li>Click <strong>Register</strong></li>\n  </ul>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"2\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-24.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-24-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-25.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-25-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n    <h2 id=\"add-the-app-to-the-user-flow\">\n      Add the App to the User flow\n    </h2>\n\n  <ul>\n    <li>Browse to <strong>Entra ID &gt; External Identities &gt; User flows</strong></li><li>Select the user flow by clicking on it</li><li>Select <strong>Applications</strong></li><li>Select <strong>Add Application</strong></li><li>A list of applications will be displayed. Select the application registered in the previous step (BlazorEntraApp) by checking the box to the left of the name.</li><li>Select <strong>Select</strong></li>\n  </ul>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"5\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-26.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-26-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-27-2.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-27-2-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-28-2.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-28-2-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-29-2.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-29-2-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-30-2.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-30-2-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n    <h2 id=\"create-a-client-secret\">\n      Create a Client Secret\n    </h2>\n\n  <p>\n    Eventually our Blazor Server app will be treated as a <em>confidential client</em> and as such it will have to provide a <em>client secret </em>(or certificate).&nbsp; The simplest approach is a <strong>client secret</strong>, so we need to add one to the Entra External ID app registration.&nbsp; To do this:\n  </p>\n\n  <ul>\n    <li>In <strong>Entra ID</strong></li><li>Select<strong> App registrations</strong> from the left-hand menu</li><li>Select <strong>App applications</strong> and click the relevant <strong>App registration</strong></li><li>Select <strong>Certificates and secrets</strong></li><li>Select <strong>New client secret</strong></li><li>Enter</li><ul><li>a <strong>Description</strong> for the secret (any suitable name)</li><li>select a value from <strong>Expires</strong> drop-down list (I selected 12 months)</li><li>Click<strong> Add</strong></li></ul><li>Some documentation states that one should immediately copy and paste the secret to keep it in a safe place because this is the only opportunity to do so. I found this to be incorrect, but it may be better to ere on the side of caution in case my situation was a one-off.<font color=\"#02192b\"><b></b></font></li>\n  </ul>\n\n  <p>\n    Why is this required? Because a Blazor server app runs on the server, not the browser, it is considered to be a <strong>confidential client</strong> in OAuth terms. Confidential clients must prove their identity to Entra External ID by presenting a client credential. In our case we are using a client secret, but a certificate could be an alternative.\n  </p>\n\n  <div  class=\"gallery-wrapper\">\n    <div class=\"gallery\" data-columns=\"5\">\n      <figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-40.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-40-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-41.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-41-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-42.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-42-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-43.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-43-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure><figure class=\"gallery__item\">\n      <a href=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-44.png\" data-size=\"1592x941\">\n        <img loading=\"lazy\" src=\"https://blazorcode.uk/blazorauthentication/media/posts/3/gallery/Entra-44-thumbnail.png\" height=\"454\" width=\"768\" alt=\"\" >\n      </a>\n      \n    </figure>\n    </div>\n  </div>\n\n  <p>\n    <strong>Warning: Secrets expire</strong> - if an error along the lines of \"Msal Client Exception: One client credential type required...\" check the expiry date of the client secret.\n  </p>\n\n    <h2 id=\"resources\">\n      Resources\n    </h2>\n\n  <p>\n    Code with Anjuli - <a href=\"https://youtu.be/WdhHX-d-Uqs\" target=\"_blank\">Microsoft Entra External ID for customers</a>\n  </p>\n\n  <p>\n    Microsoft Learn -&nbsp;<a href=\"https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-user-flow-sign-up-sign-in-customers\" target=\"_blank\">Create a sign-up and sign-in user flow for an external tenant app</a>\n  </p>\n\n  <p>\n    Microsoft Learn -&nbsp;<a href=\"https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-user-flow-add-application\" target=\"_blank\">Add your application to the user flow</a>\n  </p>",
            "author": {
                "name": "Christopher J Bell"
            },
            "tags": [
            ],
            "date_published": "2025-11-10T10:18:46+00:00",
            "date_modified": "2025-11-28T15:38:38+00:00"
        },
        {
            "id": "https://blazorcode.uk/blazorauthentication/microsoft-entra-external-id/",
            "url": "https://blazorcode.uk/blazorauthentication/microsoft-entra-external-id/",
            "title": "Microsoft Entra External ID",
            "summary": "Introduction First, a confession.&nbsp; I have previously used Azure B2C, the predecessor to Entra External ID.&nbsp; However, Azure B2C is no longer available for new&hellip;",
            "content_html": "\n    <h2 id=\"introduction\">\n      Introduction\n    </h2>\n\n  <p>\n    First, a confession.&nbsp; I have previously used Azure B2C, the predecessor to Entra External ID.&nbsp; However, Azure B2C is no longer available for new customers, although existing tenants can continue to use it and it will continue to be supported, probably until 2030.\n  </p>\n\n  <p>\n    In many ways Entra External ID is the obvious security provider to use with a Blazor application.&nbsp; Both Blazor and Entra External ID are Microsoft products and one would hope their integration will be straightforward.\n  </p>\n\n    <h2 id=\"cost\">\n      Cost\n    </h2>\n\n  <p>\n    Entra External ID is free for the first 50,000 MAU for email authentication. SMS and voice Multifactor Authentication (MFA) are billed per attempt.\n  </p>\n\n  <p>\n    \n  </p>\n\n  <p>\n    \n  </p>\n\n    <h2 id=\"resources\">\n      Resources\n    </h2>\n\n  <p>\n    \n  </p>",
            "author": {
                "name": "Christopher J Bell"
            },
            "tags": [
            ],
            "date_published": "2025-11-09T10:26:40+00:00",
            "date_modified": "2025-11-09T11:44:36+00:00"
        },
        {
            "id": "https://blazorcode.uk/blazorauthentication/what-and-how/",
            "url": "https://blazorcode.uk/blazorauthentication/what-and-how/",
            "title": "Securing a Blazor Web App - Why?",
            "summary": "The answer to the \"Why?\" question is simple. For any application beyond a static website, the data being entered is almost always specific to the&hellip;",
            "content_html": "\n  <p>\n    The answer to the \"Why?\" question is simple.\n  </p>\n\n  <p>\n    For any application beyond a static website, the data being entered is almost always specific to the user providing it. In such cases, it's essential to keep that data hidden from other users. Therefore, we need a mechanism that allows each user to log in to the application and ensures that they can access only the data they \"own.\"\n  </p>\n\n  <p>\n    <strong>Authentication</strong> is the process of identifying and verifying a user. This is typically achieved through a login form, which requires the user to provide credentials—commonly an identifier such as an email address and a secret such as a password.<br>\n  </p>\n\n  <p>\n    Once a user has gained access to the application, they may be restricted to certain menu options depending on who they are or what role they hold. This is known as <strong>Authorisation</strong>.&nbsp;<br>\n  </p>\n\n    <h2 id=\"how\">\n      How\n    </h2>\n\n  <p>\n    There are a number of ways of adding security to a Blazor application. The most obvious is to write one's own security layer - but I am going to dismiss this immediately. The reason for this being that it would entail recording users' logins and passwords. Ensuring this data is not exposed to any 'leaks' adds a responsibility I am not prepared to take.\n  </p>\n\n  <p>\n    Fortunately there are a number of third-party security applications that we can hook into from a Blazor application.&nbsp; All these have the advantage that the provider bears the responsibility for data security.\n  </p>\n\n  <p>\n    From some brief research the following third party security providers rose to the top.\n  </p>\n\n  <ul>\n    <li>Microsoft Entra External ID</li><li>Amazon Cognito</li><li>Google Firebase Authentication</li>\n  </ul>\n\n  <p>\n    When searching for security providers, my criteria included that the service should be free for up to 50,000 MAU (Monthly User Accesses). I believe the three listed above fulfil this criterion, though this may be open to interpretation.\n  </p>\n\n  <p>\n    It is also probably no coincidence that the three providers also happen to be amongst the largest IT companies. To a certain extent I take some comfort from this - hopefully they can be trusted to safeguard users' credentials.\n  </p>\n\n  <p>\n    I am going to investigate all three providers and report my findings. My principal focus will be on how simple it is to integrate these security providers with a basic Blazor server web application.\n  </p>\n\n  <p>\n    At this stage I am aware that I have have unknown unknowns and this is a journey into the unknown!\n  </p>",
            "author": {
                "name": "Christopher J Bell"
            },
            "tags": [
            ],
            "date_published": "2025-11-08T10:05:27+00:00",
            "date_modified": "2025-11-28T11:14:11+00:00"
        }
    ]
}
