 b24beb3154
			
		
	
	
		b24beb3154
		
	
	
	
	
		
			
			This commit integrates ASP.NET Core Identity into the application to enable user registration, login, and management. This lays the groundwork for securing data per user.
**Key Changes:**
*   **DbContext Configuration:**
    *   Modified `ApplicationDbContext.cs` to inherit from `IdentityDbContext<IdentityUser>`.
    *   Removed an unnecessary `using` statement from `ApplicationDbContext.cs`.
*   **Program.cs Setup:**
    *   Configured `AddDefaultIdentity<IdentityUser>` with `AddEntityFrameworkStores<ApplicationDbContext>()` to register Identity services.
    *   Ensured correct ordering of `UseAuthentication()` and `UseAuthorization()` middleware.
    *   Added `app.MapRazorPages()` to enable the Identity UI pages.
    *   Verified core package versions in `turf_tasker.csproj` for consistency across EF Core and Identity components (`8.0.6`).
*   **Identity UI:**
    *   Scaffolded ASP.NET Core Identity pages (Login, Register, Manage, etc.) to provide the user interface for authentication.
    *   Added a `_LoginPartial.cshtml` partial view to the `Views/Shared` folder.
    *   Rendered `_LoginPartial` in `Views/Shared/_Layout.cshtml` to display login/register/logout links in the navigation bar.
*   **Migrations:**
    *   Created and applied a new migration (`AddIdentitySchema`) to create the necessary ASP.NET Core Identity database tables (e.g., `AspNetUsers`, `AspNetRoles`).
		
	
			
		
			
				
	
	
		
			188 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			188 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| // Licensed to the .NET Foundation under one or more agreements.
 | |
| // The .NET Foundation licenses this file to you under the MIT license.
 | |
| #nullable disable
 | |
| 
 | |
| using System;
 | |
| using System.ComponentModel.DataAnnotations;
 | |
| using System.Globalization;
 | |
| using System.Linq;
 | |
| using System.Text;
 | |
| using System.Text.Encodings.Web;
 | |
| using System.Threading.Tasks;
 | |
| using Microsoft.AspNetCore.Identity;
 | |
| using Microsoft.AspNetCore.Mvc;
 | |
| using Microsoft.AspNetCore.Mvc.RazorPages;
 | |
| using Microsoft.Extensions.Logging;
 | |
| 
 | |
| namespace turf_tasker.Areas.Identity.Pages.Account.Manage
 | |
| {
 | |
|     public class EnableAuthenticatorModel : PageModel
 | |
|     {
 | |
|         private readonly UserManager<IdentityUser> _userManager;
 | |
|         private readonly ILogger<EnableAuthenticatorModel> _logger;
 | |
|         private readonly UrlEncoder _urlEncoder;
 | |
| 
 | |
|         private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
 | |
| 
 | |
|         public EnableAuthenticatorModel(
 | |
|             UserManager<IdentityUser> userManager,
 | |
|             ILogger<EnableAuthenticatorModel> logger,
 | |
|             UrlEncoder urlEncoder)
 | |
|         {
 | |
|             _userManager = userManager;
 | |
|             _logger = logger;
 | |
|             _urlEncoder = urlEncoder;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
 | |
|         ///     directly from your code. This API may change or be removed in future releases.
 | |
|         /// </summary>
 | |
|         public string SharedKey { get; set; }
 | |
| 
 | |
|         /// <summary>
 | |
|         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
 | |
|         ///     directly from your code. This API may change or be removed in future releases.
 | |
|         /// </summary>
 | |
|         public string AuthenticatorUri { get; set; }
 | |
| 
 | |
|         /// <summary>
 | |
|         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
 | |
|         ///     directly from your code. This API may change or be removed in future releases.
 | |
|         /// </summary>
 | |
|         [TempData]
 | |
|         public string[] RecoveryCodes { get; set; }
 | |
| 
 | |
|         /// <summary>
 | |
|         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
 | |
|         ///     directly from your code. This API may change or be removed in future releases.
 | |
|         /// </summary>
 | |
|         [TempData]
 | |
|         public string StatusMessage { get; set; }
 | |
| 
 | |
|         /// <summary>
 | |
|         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
 | |
|         ///     directly from your code. This API may change or be removed in future releases.
 | |
|         /// </summary>
 | |
|         [BindProperty]
 | |
|         public InputModel Input { get; set; }
 | |
| 
 | |
|         /// <summary>
 | |
|         ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
 | |
|         ///     directly from your code. This API may change or be removed in future releases.
 | |
|         /// </summary>
 | |
|         public class InputModel
 | |
|         {
 | |
|             /// <summary>
 | |
|             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
 | |
|             ///     directly from your code. This API may change or be removed in future releases.
 | |
|             /// </summary>
 | |
|             [Required]
 | |
|             [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
 | |
|             [DataType(DataType.Text)]
 | |
|             [Display(Name = "Verification Code")]
 | |
|             public string Code { get; set; }
 | |
|         }
 | |
| 
 | |
|         public async Task<IActionResult> OnGetAsync()
 | |
|         {
 | |
|             var user = await _userManager.GetUserAsync(User);
 | |
|             if (user == null)
 | |
|             {
 | |
|                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | |
|             }
 | |
| 
 | |
|             await LoadSharedKeyAndQrCodeUriAsync(user);
 | |
| 
 | |
|             return Page();
 | |
|         }
 | |
| 
 | |
|         public async Task<IActionResult> OnPostAsync()
 | |
|         {
 | |
|             var user = await _userManager.GetUserAsync(User);
 | |
|             if (user == null)
 | |
|             {
 | |
|                 return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
 | |
|             }
 | |
| 
 | |
|             if (!ModelState.IsValid)
 | |
|             {
 | |
|                 await LoadSharedKeyAndQrCodeUriAsync(user);
 | |
|                 return Page();
 | |
|             }
 | |
| 
 | |
|             // Strip spaces and hyphens
 | |
|             var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
 | |
| 
 | |
|             var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
 | |
|                 user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
 | |
| 
 | |
|             if (!is2faTokenValid)
 | |
|             {
 | |
|                 ModelState.AddModelError("Input.Code", "Verification code is invalid.");
 | |
|                 await LoadSharedKeyAndQrCodeUriAsync(user);
 | |
|                 return Page();
 | |
|             }
 | |
| 
 | |
|             await _userManager.SetTwoFactorEnabledAsync(user, true);
 | |
|             var userId = await _userManager.GetUserIdAsync(user);
 | |
|             _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
 | |
| 
 | |
|             StatusMessage = "Your authenticator app has been verified.";
 | |
| 
 | |
|             if (await _userManager.CountRecoveryCodesAsync(user) == 0)
 | |
|             {
 | |
|                 var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
 | |
|                 RecoveryCodes = recoveryCodes.ToArray();
 | |
|                 return RedirectToPage("./ShowRecoveryCodes");
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 return RedirectToPage("./TwoFactorAuthentication");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user)
 | |
|         {
 | |
|             // Load the authenticator key & QR code URI to display on the form
 | |
|             var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
 | |
|             if (string.IsNullOrEmpty(unformattedKey))
 | |
|             {
 | |
|                 await _userManager.ResetAuthenticatorKeyAsync(user);
 | |
|                 unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
 | |
|             }
 | |
| 
 | |
|             SharedKey = FormatKey(unformattedKey);
 | |
| 
 | |
|             var email = await _userManager.GetEmailAsync(user);
 | |
|             AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
 | |
|         }
 | |
| 
 | |
|         private string FormatKey(string unformattedKey)
 | |
|         {
 | |
|             var result = new StringBuilder();
 | |
|             int currentPosition = 0;
 | |
|             while (currentPosition + 4 < unformattedKey.Length)
 | |
|             {
 | |
|                 result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' ');
 | |
|                 currentPosition += 4;
 | |
|             }
 | |
|             if (currentPosition < unformattedKey.Length)
 | |
|             {
 | |
|                 result.Append(unformattedKey.AsSpan(currentPosition));
 | |
|             }
 | |
| 
 | |
|             return result.ToString().ToLowerInvariant();
 | |
|         }
 | |
| 
 | |
|         private string GenerateQrCodeUri(string email, string unformattedKey)
 | |
|         {
 | |
|             return string.Format(
 | |
|                 CultureInfo.InvariantCulture,
 | |
|                 AuthenticatorUriFormat,
 | |
|                 _urlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"),
 | |
|                 _urlEncoder.Encode(email),
 | |
|                 unformattedKey);
 | |
|         }
 | |
|     }
 | |
| }
 |