作成中 Blazor Server アプリに Basic 認証機能を追加する

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:

1. 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.

1. Open the `Startup.cs` file in your Blazor server project.

2. In the `ConfigureServices` method, add the following code to enable authentication:
```csharp
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();
}
```

3. In the `Configure` method, add the following code to configure the pipeline:
```csharp
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");
    });

    // ...
}
```

4. Add authentication support to a Blazor component/page:
For example, let's protect a component called `MyProtectedComponent`:

- In the `MyProtectedComponent.razor` file, add the following code at the top:
```html
@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 access `MyProtectedComponent`.

5. Test the authentication:
Deploy your Blazor app to IIS and access it from Safari. When accessing the protected `MyProtectedComponent`, 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:

1. 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.

2. Add the necessary packages:
   - Install the `System.DirectoryServices.AccountManagement` NuGet package in your project. This package provides the required APIs to work with AD.

3. Update the `ConfigureServices` method in the `Startup.cs` file:
   - Import the required namespaces:
     ```csharp
     using System.DirectoryServices.AccountManagement;
     ```

   - Update the `ConfigureServices` method as follows:
     ```csharp
     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.

4. 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:

1. Update the `ConfigureServices` method in the `Startup.cs` file:
   - Import the required namespaces:
     ```csharp
     using System.DirectoryServices.AccountManagement;
     using System.Threading.Tasks;
     ```

   - Update the `ConfigureServices` method as follows:
     ```csharp
     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 the `CookieAuthenticationOptions`. 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.

2. Implement the `GetUserPrincipalAsync` method:
   - Add the following method to your `Startup.cs` file:
     ```csharp
     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).

3. 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:
     ```csharp
     @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 the `user.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:
```html
@inherits LayoutComponentBase
@inject AuthenticationStateProvider AuthenticationStateProvider

<CascadingValue Value="userName" Name="UserName">
    @Body
</CascadingValue>
```
```csharp
@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:

```csharp
@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.