To implement authentication in a .NET Blazor server web app under the specified conditions (Safari browser, deployed in IIS with Basic Authentication enabled), you can follow these steps:
-
Configure IIS for Basic Authentication:
- Open Internet Information Services (IIS) Manager.
- Select your application in the Sites tree on the left.
- Double-click the "Authentication" feature.
- Disable Windows Authentication and any other authentication methods except Basic Authentication.
- Ensure Basic Authentication is enabled.
-
Open the
Startup.cs
file in your Blazor server project. -
In the
ConfigureServices
method, add the following code to enable authentication:
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(60); // Set the expiration time as desired
options.SlidingExpiration = true;
});
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
services.AddRazorPages();
services.AddServerSideBlazor();
}
- In the
Configure
method, add the following code to configure the pipeline:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
// ...
}
- Add authentication support to a Blazor component/page:
For example, let's protect a component calledMyProtectedComponent
:
- In the
MyProtectedComponent.razor
file, add the following code at the top:
@attribute [Authorize]
@page "/protectedpage"
<h3>Protected Page</h3>
<p>This page requires authentication to access.</p>
- This
Authorize
attribute will require the user to be authenticated to accessMyProtectedComponent
.
- Test the authentication:
Deploy your Blazor app to IIS and access it from Safari. When accessing the protectedMyProtectedComponent
, the app should prompt for basic authentication credentials. Upon successful authentication, the user should be able to access the protected component.
Note: It's important to configure the authentication and authorization settings based on your specific requirements and security considerations. The example provided above demonstrates a basic implementation using Basic Authentication and protecting a specific component/page.
Add authorization for users who are in a specific security group in AD
You can add the feature of authorization for users who are in a specific security group in Active Directory (AD) to your Blazor server app while still using Basic authentication. Here's how you can implement this:
-
Configure your Blazor app for AD authentication:
- Follow the steps mentioned earlier to enable Basic authentication in your app as described in the previous responses.
-
Add the necessary packages:
- Install the
System.DirectoryServices.AccountManagement
NuGet package in your project. This package provides the required APIs to work with AD.
- Install the
-
Update the
ConfigureServices
method in theStartup.cs
file:-
Import the required namespaces:
using System.DirectoryServices.AccountManagement;
-
Update the
ConfigureServices
method as follows:public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(60); // Set the expiration time as desired options.SlidingExpiration = true; }); services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .RequireRole("YourSecurityGroup") // Replace with the name of your specific security group in AD .Build(); }); services.AddRazorPages(); services.AddServerSideBlazor(); }
-
In the
RequireRole
method, specify the name of the security group in AD that you want to restrict access to. Replace"YourSecurityGroup"
with the actual name of your security group.
-
-
Test the authorization:
- Deploy your Blazor app to IIS with Basic authentication enabled.
- When users access the app and provide their credentials through Basic authentication, the app will authenticate them.
- Additionally, the app will check if the authenticated user belongs to the specified security group in AD (the group name you provided in the
RequireRole
method). - Only users who are both authenticated and in the specified security group will be authorized to access the protected pages/components.
With these steps, you can enforce authorization for only users who are in a specific security group in AD, while still using Basic authentication for authentication.
Retrieve authorized user's name
To retrieve the authorized user's information, such as surname and given name, and display it in your Blazor server app in Japanese (Chinese characters), you can utilize the System.DirectoryServices.AccountManagement
namespace and the UserPrincipal
class. Here's how you can modify your code to achieve this:
-
Update the
ConfigureServices
method in theStartup.cs
file:-
Import the required namespaces:
using System.DirectoryServices.AccountManagement; using System.Threading.Tasks;
-
Update the
ConfigureServices
method as follows:public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(60); // Set the expiration time as desired options.SlidingExpiration = true; options.Events = new CookieAuthenticationEvents { OnSigningIn = async context => { // Retrieve the user's AD information var userPrincipal = await GetUserPrincipalAsync(context.Principal.Identity.Name); if (userPrincipal != null) { // Set the user's surname and given name as claims context.Principal.Identities.First().AddClaim(new Claim("Surname", userPrincipal.Surname)); context.Principal.Identities.First().AddClaim(new Claim("GivenName", userPrincipal.GivenName)); } } }; }); services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .RequireRole("YourSecurityGroup") // Replace with the name of your specific security group in AD .Build(); }); services.AddRazorPages(); services.AddServerSideBlazor(); }
-
In the code above, we have added an
OnSigningIn
event handler to theCookieAuthenticationOptions
. This event is triggered when a user signs in successfully. -
Inside the event handler, the
GetUserPrincipalAsync
method is called to retrieve the user's AD information. -
If the user information is obtained successfully, it is added as claims to the user's principal object.
-
-
Implement the
GetUserPrincipalAsync
method:-
Add the following method to your
Startup.cs
file:private async Task<UserPrincipal> GetUserPrincipalAsync(string username) { using (var context = new PrincipalContext(ContextType.Domain)) { var userPrincipal = await Task.Run(() => UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username)); return userPrincipal; } }
-
This method uses the
UserPrincipal
class to retrieve the user's AD information based on the provided username (SAM Account Name).
-
-
Access the user's information in your Blazor component or page:
-
In the Blazor component or page where you want to display the user's information, you can inject the
AuthenticationStateProvider
service and retrieve the claims. For example:@inject AuthenticationStateProvider AuthenticationStateProvider <p>Welcome, @DisplayName</p> @code { private string DisplayName { get; set; } protected override async Task OnInitializedAsync() { var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); var user = authenticationState.User; DisplayName = user.FindFirstValue("Surname") + " " + user.FindFirstValue("GivenName"); } }
-
In the example above, the
AuthenticationStateProvider
service is injected, and the user's claims for surname and given name are retrieved using theuser.FindFirstValue
method and then displayed in the component.
-
Cascade the retrieved user info
By following these steps, you can retrieve the authorized user's information from AD, such as surname and given name, and display it in your Blazor server app using Basic authentication. The retrieved information will be in Japanese (Chinese characters) since AD supports multilingual attributes.
Yes, it is possible to include the code snippet in the default MainLayout.razor
file and use the retrieved surname and given name as cascading parameters in other Razor files. Here's an example:
In the MainLayout.razor
file:
@inherits LayoutComponentBase
@inject AuthenticationStateProvider AuthenticationStateProvider
<CascadingValue Value="userName" Name="UserName">
@Body
</CascadingValue>
@code {
private string userName;
protected override async Task OnInitializedAsync()
{
var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authenticationState != null && authenticationState.User.Identity.IsAuthenticated)
{
var user = authenticationState.User;
var surname = user.FindFirstValue("Surname");
var givenName = user.FindFirstValue("GivenName");
userName = $"{surname} {givenName}";
}
}
}
In this example, the MainLayout.razor
component injects the UserService
and AuthenticationStateProvider
. During the initialization (OnInitializedAsync
method), the user's surname and given name are retrieved from the authentication state and stored in the UserService
properties (Surname
and GivenName
).
The Surname
and GivenName
values are then cascaded down to child components using the CascadingValue
component. Child components can access these values as cascading parameters.
In other Razor components/pages where you want to use the cascaded surname and given name, you can define the cascading parameters like this:
@code {
[CascadingParameter(Name = "userName")]
public string userName { get; set; }
}
Now, within these components/pages, you can directly access the Surname
and GivenName
properties, which will hold the cascaded values from the MainLayout
component.
Note: Make sure to inject the UserService
in the MainLayout.razor
file and define the Surname
and GivenName
properties as public properties in the UserService
class for them to be accessible as cascading values.