feat: Integrate ASP.NET Core Identity for user authentication
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`).
			
			
This commit is contained in:
		
							parent
							
								
									60567d7969
								
							
						
					
					
						commit
						b24beb3154
					
				
					 79 changed files with 5246 additions and 12 deletions
				
			
		
							
								
								
									
										417
									
								
								auth.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								auth.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,417 @@ | |||
| diff --git a/Data/ApplicationDbContext.cs b/Data/ApplicationDbContext.cs | ||||
| index e919f1a..0f29688 100644 | ||||
| --- a/Data/ApplicationDbContext.cs | ||||
| +++ b/Data/ApplicationDbContext.cs | ||||
| @@ -1,12 +1,20 @@ | ||||
| +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; | ||||
| +using Microsoft.AspNetCore.Identity; | ||||
| +using Microsoft.AspNetCore.Identity.UI; | ||||
|  using Microsoft.EntityFrameworkCore; | ||||
|  using turf_tasker.Models; | ||||
|   | ||||
|  namespace turf_tasker.Data; | ||||
|   | ||||
| -public class ApplicationDbContext : DbContext | ||||
| +public class ApplicationDbContext : IdentityDbContext<IdentityUser> | ||||
|  { | ||||
| +    private readonly DbContextOptions<ApplicationDbContext> _options; | ||||
| + | ||||
|      public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) | ||||
| -        : base(options) { } | ||||
| +        : base(options) | ||||
| +    { | ||||
| +        _options = options; | ||||
| +    } | ||||
|       | ||||
|      public DbSet<LawnCareEvent> LawnCareEvents { get; set; } | ||||
|       | ||||
| diff --git a/Migrations/ApplicationDbContextModelSnapshot.cs b/Migrations/ApplicationDbContextModelSnapshot.cs | ||||
| index 320c9f9..c57d1dc 100644 | ||||
| --- a/Migrations/ApplicationDbContextModelSnapshot.cs | ||||
| +++ b/Migrations/ApplicationDbContextModelSnapshot.cs | ||||
| @@ -15,7 +15,203 @@ namespace turf_tasker.Migrations | ||||
|          protected override void BuildModel(ModelBuilder modelBuilder) | ||||
|          { | ||||
|  #pragma warning disable 612, 618 | ||||
| -            modelBuilder.HasAnnotation("ProductVersion", "9.0.6"); | ||||
| +            modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => | ||||
| +                { | ||||
| +                    b.Property<string>("Id") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("ConcurrencyStamp") | ||||
| +                        .IsConcurrencyToken() | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("Name") | ||||
| +                        .HasMaxLength(256) | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("NormalizedName") | ||||
| +                        .HasMaxLength(256) | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.HasKey("Id"); | ||||
| + | ||||
| +                    b.HasIndex("NormalizedName") | ||||
| +                        .IsUnique() | ||||
| +                        .HasDatabaseName("RoleNameIndex"); | ||||
| + | ||||
| +                    b.ToTable("AspNetRoles", (string)null); | ||||
| +                }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => | ||||
| +                { | ||||
| +                    b.Property<int>("Id") | ||||
| +                        .ValueGeneratedOnAdd() | ||||
| +                        .HasColumnType("INTEGER"); | ||||
| + | ||||
| +                    b.Property<string>("ClaimType") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("ClaimValue") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("RoleId") | ||||
| +                        .IsRequired() | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.HasKey("Id"); | ||||
| + | ||||
| +                    b.HasIndex("RoleId"); | ||||
| + | ||||
| +                    b.ToTable("AspNetRoleClaims", (string)null); | ||||
| +                }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => | ||||
| +                { | ||||
| +                    b.Property<string>("Id") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<int>("AccessFailedCount") | ||||
| +                        .HasColumnType("INTEGER"); | ||||
| + | ||||
| +                    b.Property<string>("ConcurrencyStamp") | ||||
| +                        .IsConcurrencyToken() | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("Email") | ||||
| +                        .HasMaxLength(256) | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<bool>("EmailConfirmed") | ||||
| +                        .HasColumnType("INTEGER"); | ||||
| + | ||||
| +                    b.Property<bool>("LockoutEnabled") | ||||
| +                        .HasColumnType("INTEGER"); | ||||
| + | ||||
| +                    b.Property<DateTimeOffset?>("LockoutEnd") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("NormalizedEmail") | ||||
| +                        .HasMaxLength(256) | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("NormalizedUserName") | ||||
| +                        .HasMaxLength(256) | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("PasswordHash") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("PhoneNumber") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<bool>("PhoneNumberConfirmed") | ||||
| +                        .HasColumnType("INTEGER"); | ||||
| + | ||||
| +                    b.Property<string>("SecurityStamp") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<bool>("TwoFactorEnabled") | ||||
| +                        .HasColumnType("INTEGER"); | ||||
| + | ||||
| +                    b.Property<string>("UserName") | ||||
| +                        .HasMaxLength(256) | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.HasKey("Id"); | ||||
| + | ||||
| +                    b.HasIndex("NormalizedEmail") | ||||
| +                        .HasDatabaseName("EmailIndex"); | ||||
| + | ||||
| +                    b.HasIndex("NormalizedUserName") | ||||
| +                        .IsUnique() | ||||
| +                        .HasDatabaseName("UserNameIndex"); | ||||
| + | ||||
| +                    b.ToTable("AspNetUsers", (string)null); | ||||
| +                }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => | ||||
| +                { | ||||
| +                    b.Property<int>("Id") | ||||
| +                        .ValueGeneratedOnAdd() | ||||
| +                        .HasColumnType("INTEGER"); | ||||
| + | ||||
| +                    b.Property<string>("ClaimType") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("ClaimValue") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("UserId") | ||||
| +                        .IsRequired() | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.HasKey("Id"); | ||||
| + | ||||
| +                    b.HasIndex("UserId"); | ||||
| + | ||||
| +                    b.ToTable("AspNetUserClaims", (string)null); | ||||
| +                }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => | ||||
| +                { | ||||
| +                    b.Property<string>("LoginProvider") | ||||
| +                        .HasMaxLength(128) | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("ProviderKey") | ||||
| +                        .HasMaxLength(128) | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("ProviderDisplayName") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("UserId") | ||||
| +                        .IsRequired() | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.HasKey("LoginProvider", "ProviderKey"); | ||||
| + | ||||
| +                    b.HasIndex("UserId"); | ||||
| + | ||||
| +                    b.ToTable("AspNetUserLogins", (string)null); | ||||
| +                }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => | ||||
| +                { | ||||
| +                    b.Property<string>("UserId") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("RoleId") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.HasKey("UserId", "RoleId"); | ||||
| + | ||||
| +                    b.HasIndex("RoleId"); | ||||
| + | ||||
| +                    b.ToTable("AspNetUserRoles", (string)null); | ||||
| +                }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => | ||||
| +                { | ||||
| +                    b.Property<string>("UserId") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("LoginProvider") | ||||
| +                        .HasMaxLength(128) | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("Name") | ||||
| +                        .HasMaxLength(128) | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.Property<string>("Value") | ||||
| +                        .HasColumnType("TEXT"); | ||||
| + | ||||
| +                    b.HasKey("UserId", "LoginProvider", "Name"); | ||||
| + | ||||
| +                    b.ToTable("AspNetUserTokens", (string)null); | ||||
| +                }); | ||||
|   | ||||
|              modelBuilder.Entity("turf_tasker.Models.LawnCareEvent", b => | ||||
|                  { | ||||
| @@ -77,6 +273,57 @@ namespace turf_tasker.Migrations | ||||
|   | ||||
|                      b.ToTable("LawnCareTips"); | ||||
|                  }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => | ||||
| +                { | ||||
| +                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) | ||||
| +                        .WithMany() | ||||
| +                        .HasForeignKey("RoleId") | ||||
| +                        .OnDelete(DeleteBehavior.Cascade) | ||||
| +                        .IsRequired(); | ||||
| +                }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => | ||||
| +                { | ||||
| +                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||
| +                        .WithMany() | ||||
| +                        .HasForeignKey("UserId") | ||||
| +                        .OnDelete(DeleteBehavior.Cascade) | ||||
| +                        .IsRequired(); | ||||
| +                }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => | ||||
| +                { | ||||
| +                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||
| +                        .WithMany() | ||||
| +                        .HasForeignKey("UserId") | ||||
| +                        .OnDelete(DeleteBehavior.Cascade) | ||||
| +                        .IsRequired(); | ||||
| +                }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => | ||||
| +                { | ||||
| +                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) | ||||
| +                        .WithMany() | ||||
| +                        .HasForeignKey("RoleId") | ||||
| +                        .OnDelete(DeleteBehavior.Cascade) | ||||
| +                        .IsRequired(); | ||||
| + | ||||
| +                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||
| +                        .WithMany() | ||||
| +                        .HasForeignKey("UserId") | ||||
| +                        .OnDelete(DeleteBehavior.Cascade) | ||||
| +                        .IsRequired(); | ||||
| +                }); | ||||
| + | ||||
| +            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => | ||||
| +                { | ||||
| +                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) | ||||
| +                        .WithMany() | ||||
| +                        .HasForeignKey("UserId") | ||||
| +                        .OnDelete(DeleteBehavior.Cascade) | ||||
| +                        .IsRequired(); | ||||
| +                }); | ||||
|  #pragma warning restore 612, 618 | ||||
|          } | ||||
|      } | ||||
| diff --git a/Program.cs b/Program.cs | ||||
| index dcf9de2..701e4e2 100644 | ||||
| --- a/Program.cs | ||||
| +++ b/Program.cs | ||||
| @@ -1,20 +1,22 @@ | ||||
|  using Microsoft.EntityFrameworkCore; | ||||
|  using turf_tasker.Data; | ||||
| +using Microsoft.AspNetCore.Identity; | ||||
|   | ||||
|  var builder = WebApplication.CreateBuilder(args); | ||||
|   | ||||
| -// Add services to the container. | ||||
|  builder.Services.AddDbContext<ApplicationDbContext>(options => | ||||
|      options.UseSqlite( | ||||
|          builder.Configuration.GetConnectionString("DefaultConnection") | ||||
|      ) | ||||
|  ); | ||||
|   | ||||
| +builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) | ||||
| +    .AddEntityFrameworkStores<ApplicationDbContext>(); | ||||
| + | ||||
|  builder.Services.AddControllersWithViews(); | ||||
|   | ||||
|  var app = builder.Build(); | ||||
|   | ||||
| -// Configure the HTTP request pipeline. | ||||
|  if (!app.Environment.IsDevelopment()) | ||||
|  { | ||||
|      app.UseExceptionHandler("/Home/Error"); | ||||
| @@ -26,6 +28,7 @@ app.UseStaticFiles(); | ||||
|   | ||||
|  app.UseRouting(); | ||||
|   | ||||
| +app.UseAuthentication(); | ||||
|  app.UseAuthorization(); | ||||
|   | ||||
|  app.MapControllerRoute( | ||||
| @@ -33,4 +36,6 @@ app.MapControllerRoute( | ||||
|      pattern: "{controller=Home}/{action=Index}/{id?}" | ||||
|  ); | ||||
|   | ||||
| +app.MapRazorPages(); | ||||
| + | ||||
|  app.Run(); | ||||
| \ No newline at end of file | ||||
| diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml | ||||
| index c27a958..f67aa24 100644 | ||||
| --- a/Views/Shared/_Layout.cshtml | ||||
| +++ b/Views/Shared/_Layout.cshtml | ||||
| @@ -33,6 +33,7 @@ | ||||
|                  </div> | ||||
|              </div> | ||||
|          </nav> | ||||
| +        <partial name="_LoginPartial" /> | ||||
|      </header> | ||||
|      <div class="container"> | ||||
|          <main role="main" class="pb-3"> | ||||
| diff --git a/Views/Shared/_LoginPartial.cshtml b/Views/Shared/_LoginPartial.cshtml | ||||
| index e69de29..b838e04 100644 | ||||
| --- a/Views/Shared/_LoginPartial.cshtml | ||||
| +++ b/Views/Shared/_LoginPartial.cshtml | ||||
| @@ -0,0 +1,26 @@ | ||||
| +@using Microsoft.AspNetCore.Identity | ||||
| +@inject SignInManager<IdentityUser> SignInManager | ||||
| +@inject UserManager<IdentityUser> UserManager | ||||
| + | ||||
| +<ul class="navbar-nav"> | ||||
| +    @if (SignInManager.IsSignedIn(User)) | ||||
| +    { | ||||
| +        <li class="nav-item"> | ||||
| +            <a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @UserManager.GetUserName(User)!</a> | ||||
| +        </li> | ||||
| +        <li class="nav-item"> | ||||
| +            <form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })"> | ||||
| +                <button id="logout" type="submit" class="nav-link btn btn-link text-dark">Logout</button> | ||||
| +            </form> | ||||
| +        </li> | ||||
| +    } | ||||
| +    else | ||||
| +    { | ||||
| +        <li class="nav-item"> | ||||
| +            <a class="nav-link text-dark" id="register" asp-area="Identity" asp-page="/Account/Register">Register</a> | ||||
| +        </li> | ||||
| +        <li class="nav-item"> | ||||
| +            <a class="nav-link text-dark" id="login" asp-area="Identity" asp-page="/Account/Login">Login</a> | ||||
| +        </li> | ||||
| +    } | ||||
| +</ul> | ||||
| \ No newline at end of file | ||||
| diff --git a/turf_tasker.csproj b/turf_tasker.csproj | ||||
| index dbd9ba1..2247d16 100644 | ||||
| --- a/turf_tasker.csproj | ||||
| +++ b/turf_tasker.csproj | ||||
| @@ -7,13 +7,22 @@ | ||||
|      </PropertyGroup> | ||||
|   | ||||
|      <ItemGroup> | ||||
| -      <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" /> | ||||
| -      <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" /> | ||||
| -      <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.6"> | ||||
| -        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| -        <PrivateAssets>all</PrivateAssets> | ||||
| -      </PackageReference> | ||||
| -      <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" /> | ||||
| +        <!-- Identity Packages --> | ||||
| +        <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.6" /> | ||||
| +        <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.6" /> | ||||
| + | ||||
| +        <!-- Explicitly define ALL core EF Core packages to force version alignment --> | ||||
| +        <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" /> | ||||
| +        <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.6" /> | ||||
| +        <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" /> | ||||
| +        <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" /> | ||||
| +        <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6"> | ||||
| +            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| +            <PrivateAssets>all</PrivateAssets> | ||||
| +        </PackageReference> | ||||
| + | ||||
| +        <!-- Scaffolding Package --> | ||||
| +        <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" /> | ||||
|      </ItemGroup> | ||||
|   | ||||
|  </Project> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Blake Ridgway
						Blake Ridgway