Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BWA with WinAuth article #34708

Merged
merged 7 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion aspnetcore/blazor/security/blazor-web-app-with-entra.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Secure an ASP.NET Core Blazor Web App with Microsoft Entra ID
author: guardrex
description: Learn how to secure a Blazor WebAssembly App with Microsoft Entra ID.
description: Learn how to secure a Blazor Web App with Microsoft Entra ID.
monikerRange: '>= aspnetcore-9.0'
ms.author: riande
ms.custom: mvc
Expand Down
2 changes: 1 addition & 1 deletion aspnetcore/blazor/security/blazor-web-app-with-oidc.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Secure an ASP.NET Core Blazor Web App with OpenID Connect (OIDC)
author: guardrex
description: Learn how to secure a Blazor WebAssembly App with OpenID Connect (OIDC).
description: Learn how to secure a Blazor Web App with OpenID Connect (OIDC).
monikerRange: '>= aspnetcore-8.0'
ms.author: riande
ms.custom: mvc
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
title: Secure an ASP.NET Core Blazor Web App with Windows Authentication
author: guardrex
description: Learn how to secure a Blazor Web App with Windows Authentication.
monikerRange: '>= aspnetcore-9.0'
ms.author: riande
ms.custom: mvc
ms.date: 03/25/2025
uid: blazor/security/blazor-web-app-windows-authentication
---
# Secure an ASP.NET Core Blazor Web App with Windows Authentication

<!-- UPDATE 10.0 - Enable after release

[!INCLUDE[](~/includes/not-latest-version-without-not-supported-content.md)]

-->

This article describes how to secure a Blazor Web App with [Windows Authentication](/windows-server/security/windows-authentication/windows-authentication-overview) using a sample app. For more information, see <xref:security/authentication/windowsauth>.

The app specification for the Blazor Web App:

* Adopts the [Interactive Server render mode with global interactivity](xref:blazor/components/render-modes).
* Establishes an [authorization policy](xref:security/authorization/policies) for a [Windows security identifier](/windows-server/identity/ad-ds/manage/understand-security-identifiers) to access a secure page.

## Sample app

Access the sample through the latest version folder in the Blazor samples repository with the following link. The sample is in the `BlazorWebAppWinAuthServer` folder for .NET 9 or later.

[View or download sample code](https://github.com/dotnet/blazor-samples) ([how to download](xref:blazor/fundamentals/index#sample-apps))

## Configuration

The sample app doesn't require configuration to run locally.

When deployed to a host, such as IIS, the app must adopt impersonation to run under the user's account. For more information, see <xref:security/authentication/windowsauth#impersonation>.

## Sample app code

Inspect the `Program` file in the sample app for the following API calls.

<xref:Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions.AddAuthentication%2A> is called using the <xref:Microsoft.AspNetCore.Authentication.Negotiate.NegotiateDefaults.AuthenticationScheme%2A?displayProperty=nameWithType> authentication scheme. <xref:Microsoft.Extensions.DependencyInjection.NegotiateExtensions.AddNegotiate%2A> configures the <xref:Microsoft.AspNetCore.Authentication.AuthenticationBuilder> to use Negotiate (also known as Windows, Kerberos, or NTLM) authentication, and the authentication handler supports Kerberos on Windows and Linux servers:

```csharp
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
```

<xref:Microsoft.Extensions.DependencyInjection.PolicyServiceCollectionExtensions.AddAuthorization%2A> adds authorization policy services. <xref:Microsoft.AspNetCore.Authorization.AuthorizationOptions.FallbackPolicy%2A?displayProperty=nameWithType> sets the fallback authorization policy, which is set to the default policy (<xref:Microsoft.AspNetCore.Authorization.AuthorizationOptions.DefaultPolicy%2A?displayProperty=nameWithType>). The default policy requires an authenticated user to access the app:

```csharp
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
});
```

<xref:Microsoft.Extensions.DependencyInjection.CascadingAuthenticationStateServiceCollectionExtensions.AddCascadingAuthenticationState%2A> adds cascading authentication state to the service collection. This is equivalent to placing a `CascadingAuthenticationState` component at the root of the app's component hierarchy:

```csharp
builder.Services.AddCascadingAuthenticationState();
```

An [authorization policy](xref:security/authorization/policies) is added for a [Windows security identifier (SID)](/windows-server/identity/ad-ds/manage/understand-security-identifiers). The `S-1-5-113` well-known SID in the following example indicates that the user is a local account, which restricts network sign-in to local accounts instead of "administrator" or equivalent accounts:

```csharp
builder.Services.AddAuthorizationBuilder()
.AddPolicy("LocalAccount", policy =>
policy.RequireClaim(
"http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid",
"S-1-5-113"));
```

The authorization policy is enforced by the `LocalAccountOnly` component.

`Components/Pages/LocalAccountOnly.razor`:

```razor
@page "/local-account-only"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize("LocalAccount")]

<h1>Local Account Only</h1>

<p>
You can only reach this page by satisfying the
<code>LocalAccount</code> authorization policy.
</p>
```

The `UserClaims` component lists the user's claims, which includes the user's Windows security identifiers (SIDs).

`Components/Pages/UserClaims.razor`:

```razor
@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of this attribute given that this sample also uses options.FallbackPolicy = options.DefaultPolicy?

I'd sooner remove the fallback policy part than this attribute though. I find too many people set the fallback policy to the default policy not realizing it makes [Authorize] redundant.

Copy link
Collaborator Author

@guardrex guardrex Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The presence of the attribute is merely an oversight. However, I just ran into a pre-rendering/rendering problem removing the fallback policy:

  • If components that require security (via the attribute) are linked in the sidebar and one of the links is followed, the page merely states, "Not Authorized." There's no provision to authenticate the user (yet) in the sample app. It's not automatic, and it's not clear to me (or in our main doc set article on WinAuth ... all examples use the fallback policy approach) how to authenticate a user for WinAuth on-the-fly after an app has started.
  • If the relative URL to a secure component is added to the address bar and a secure page load is forced, the Windows Security sign-in popup appears and allows credentials to be entered. However, authenticated user state is only present for prerendering of the render after authentication. The secure page flashes its secure contents and displays "Not Authorized" for final rendering.

TL;DR ☝️😆 ... I don't know how to make this sample work without the fallback policy that requires all users to authenticate as soon as the app starts.

Sample App: https://github.com/dotnet/blazor-samples/tree/main/9.0/BlazorWebAppWinAuthServer

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the sample would need a RedirectToLogin like the OIDC sample has here. It would probably best to call it something like ForceLogin even though, and I think you could get away with having the component just call NavigationManager.Refresh(forceReload: true) rather than hit the "authentication/login" minimal endpoint and redirect back, so it would be even simpler. The new top-level request induced by Refresh should be enough for the authorization middleware issue a challenge via the Windows authentication handler (NegotiateHandler).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx @halter73 ... I'll give that a shot on Monday morning. Have a great weekend! 🍻

Copy link
Collaborator Author

@guardrex guardrex Feb 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@halter73 ... I made the updates on https://github.com/dotnet/blazor-samples/pull/478/files.

Sample: https://github.com/dotnet/blazor-samples/tree/main/9.0/BlazorWebAppWinAuthServer

No 🎲🎲 yet ... Try to access a secure page, it presents the Windows Security signin popup, and then it puts the app/page into a redirect loop of death 💀 immediately after signing in.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping @halter73 ... Made the changes that you recommended ☝️ ... but it puts the app into a redirect death loop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No 🎲🎲 yet ... Try to access a secure page, it presents the Windows Security signin popup, and then it puts the app/page into a redirect loop of death 💀 immediately after signing in.

Does this happen even if you navigate directly to the secure page from the address bar? And does it happen after signing in successfully? I'm surprised that the <NotAuthorized> logic would reexecute after that.

I'll probably have to try the sample out myself to debug it. It might be a few days before I can get to it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this happen even if you navigate directly to the secure page from the address bar?

You don't get a chance to do that under this scenario. The app loads the homepage with anonymous access. You navigate to a secure page ... sign in ... and then the authorized page loads, but it loads in a 💀 redirect loop, flashing it's content over and over rapidly.

And does it happen after signing in successfully?

Yes, that's when it happens.


<PageTitle>User Claims</PageTitle>

<h1>User Claims</h1>

@if (claims.Any())
{
<ul>
@foreach (var claim in claims)
{
<li><b>@claim.Type:</b> @claim.Value</li>
}
</ul>
}

@code {
private IEnumerable<Claim> claims = [];

[CascadingParameter]
private Task<AuthenticationState>? AuthState { get; set; }

protected override async Task OnInitializedAsync()
{
if (AuthState == null)
{
return;
}

var authState = await AuthState;
claims = authState.User.Claims;
}
}
```

## Additional resources

* <xref:security/authentication/windowsauth>
* [Security identifiers (Windows Server documentation)](/windows-server/identity/ad-ds/manage/understand-security-identifiers)
2 changes: 2 additions & 0 deletions aspnetcore/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ items:
uid: blazor/security/blazor-web-app-entra
- name: Blazor Web App with OIDC
uid: blazor/security/blazor-web-app-oidc
- name: Blazor Web App with Windows Auth
uid: blazor/security/blazor-web-app-windows-authentication
- name: Static server-side rendering threats
uid: blazor/security/static-server-side-rendering
- name: Interactive server-side rendering threats
Expand Down