diff --git a/AdvancedAPI.Business/Services/AuthenticationService.cs b/AdvancedAPI.Business/Services/AuthenticationService.cs
new file mode 100644
index 0000000..9b7dc56
--- /dev/null
+++ b/AdvancedAPI.Business/Services/AuthenticationService.cs
@@ -0,0 +1,53 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using AdvancedAPI.Data.Repositories.Interfaces;
+using AdvancedAPI.Data.ViewModels.Authentication;
+using Business.Services.Interfaces;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.IdentityModel.Tokens;
+
+namespace Business.Services;
+
+///
+public class AuthenticationService : IAuthenticationService
+{
+ private readonly IIdentityRepository _identityRepository;
+ private readonly IConfiguration _configuration;
+
+ ///
+ /// Constructor.
+ ///
+ public AuthenticationService(IIdentityRepository identityRepository, IConfiguration configuration)
+ {
+ _identityRepository = identityRepository;
+ _configuration = configuration;
+ }
+
+ ///
+ public async Task Login(LoginRequestModel requestModel, CancellationToken ct = default)
+ {
+ IdentityUser? user = await _identityRepository.GetUser(requestModel.Username);
+ if (user != null && await _identityRepository.CheckPassword(user, requestModel.Password))
+ {
+ Claim[] authClaims = new[]
+ {
+ new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
+ new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
+ };
+
+ SymmetricSecurityKey authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
+
+ JwtSecurityToken token = new JwtSecurityToken(
+ issuer: _configuration["Jwt:Issuer"],
+ audience: _configuration["Jwt:Audience"],
+ expires: DateTime.Now.AddHours(3),
+ claims: authClaims,
+ signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256));
+
+ return token;
+ }
+
+ return null;
+ }
+}
diff --git a/AdvancedAPI.Business/Services/Interfaces/IAuthenticationService.cs b/AdvancedAPI.Business/Services/Interfaces/IAuthenticationService.cs
new file mode 100644
index 0000000..f975697
--- /dev/null
+++ b/AdvancedAPI.Business/Services/Interfaces/IAuthenticationService.cs
@@ -0,0 +1,15 @@
+using System.IdentityModel.Tokens.Jwt;
+using AdvancedAPI.Data.ViewModels.Authentication;
+
+namespace Business.Services.Interfaces;
+
+///
+/// Authentication service.
+///
+public interface IAuthenticationService
+{
+ ///
+ /// Logs in the user and returns a token.
+ ///
+ public Task Login(LoginRequestModel requestModel, CancellationToken ct = default);
+}
diff --git a/AdvancedAPI.Data/AdvancedAPI.Data.csproj b/AdvancedAPI.Data/AdvancedAPI.Data.csproj
index 5e4d88c..274be67 100644
--- a/AdvancedAPI.Data/AdvancedAPI.Data.csproj
+++ b/AdvancedAPI.Data/AdvancedAPI.Data.csproj
@@ -9,7 +9,13 @@
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/AdvancedAPI.Data/AdvancedApiContext.cs b/AdvancedAPI.Data/AdvancedApiContext.cs
index 55e89a7..b0e5e83 100644
--- a/AdvancedAPI.Data/AdvancedApiContext.cs
+++ b/AdvancedAPI.Data/AdvancedApiContext.cs
@@ -1,4 +1,5 @@
-using AdvancedAPI.Data.Models;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace AdvancedAPI.Data;
@@ -6,7 +7,7 @@ namespace AdvancedAPI.Data;
///
/// Database context.
///
-public class AdvancedApiContext : DbContext
+public class AdvancedApiContext : IdentityDbContext
{
///
/// Constructor.
@@ -15,9 +16,4 @@ public AdvancedApiContext(DbContextOptions options)
: base(options)
{
}
-
- ///
- /// DbSet of .
- ///
- public DbSet Houses { get; set; }
}
diff --git a/AdvancedAPI.Data/AdvancedApiContextFactory.cs b/AdvancedAPI.Data/AdvancedApiContextFactory.cs
new file mode 100644
index 0000000..8823ee6
--- /dev/null
+++ b/AdvancedAPI.Data/AdvancedApiContextFactory.cs
@@ -0,0 +1,27 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+
+namespace AdvancedAPI.Data
+{
+ ///
+ /// Factory for creating instances at design time for EF Core tooling.
+ ///
+ public class AdvancedApiContextFactory : IDesignTimeDbContextFactory
+ {
+ ///
+ /// Creates a new with design-time configuration.
+ ///
+ public AdvancedApiContext CreateDbContext(string[] args)
+ {
+ IConfigurationRoot? configuration = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json")
+ .Build();
+
+ DbContextOptionsBuilder optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
+
+ return new AdvancedApiContext(optionsBuilder.Options);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AdvancedAPI.Data/DbInitializer.cs b/AdvancedAPI.Data/DbInitializer.cs
new file mode 100644
index 0000000..b72e251
--- /dev/null
+++ b/AdvancedAPI.Data/DbInitializer.cs
@@ -0,0 +1,65 @@
+using Microsoft.AspNetCore.Identity;
+
+namespace AdvancedAPI.Data
+{
+ ///
+ /// Database initializer.
+ ///
+ public class DbInitializer
+ {
+ ///
+ /// Initialization of the database.
+ ///
+ public static async Task Initialize(IServiceProvider serviceProvider)
+ {
+ UserManager userManager = serviceProvider.GetRequiredService>();
+ RoleManager roleManager = serviceProvider.GetRequiredService>();
+
+ // Seed roles
+ await SeedRoles(roleManager);
+
+ // Seed admin user
+ await SeedAdminUser(userManager);
+ }
+
+ ///
+ /// Seeding roles into the database.
+ ///
+ private static async Task SeedRoles(RoleManager roleManager)
+ {
+ string[] roleNames = { "Admin", "User" };
+
+ foreach (string roleName in roleNames)
+ {
+ bool roleExist = await roleManager.RoleExistsAsync(roleName);
+ if (!roleExist)
+ {
+ // Create the roles and seed them to the database
+ await roleManager.CreateAsync(new IdentityRole(roleName));
+ }
+ }
+ }
+
+ ///
+ /// Seeding user into the database.
+ ///
+ private static async Task SeedAdminUser(UserManager userManager)
+ {
+ IdentityUser? adminUser = await userManager.FindByEmailAsync("admin@example.com");
+ if (adminUser == null)
+ {
+ adminUser = new IdentityUser
+ {
+ UserName = "admin@example.com",
+ Email = "admin@example.com",
+ };
+
+ IdentityResult? result = await userManager.CreateAsync(adminUser, "P@ssw0rd");
+ if (result.Succeeded)
+ {
+ await userManager.AddToRoleAsync(adminUser, "Admin");
+ }
+ }
+ }
+ }
+}
diff --git a/AdvancedAPI.Data/Migrations/20240719221124_InitialCreate.Designer.cs b/AdvancedAPI.Data/Migrations/20240719221124_InitialCreate.Designer.cs
new file mode 100644
index 0000000..93cb12e
--- /dev/null
+++ b/AdvancedAPI.Data/Migrations/20240719221124_InitialCreate.Designer.cs
@@ -0,0 +1,278 @@
+//
+using System;
+using AdvancedAPI.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace AdvancedAPI.Data.Migrations
+{
+ [DbContext(typeof(AdvancedApiContext))]
+ [Migration("20240719221124_InitialCreate")]
+ partial class InitialCreate
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.32")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex")
+ .HasFilter("[NormalizedName] IS NOT NULL");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1);
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("bit");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1);
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("RoleId")
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Value")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/AdvancedAPI.Data/Migrations/20240719221124_InitialCreate.cs b/AdvancedAPI.Data/Migrations/20240719221124_InitialCreate.cs
new file mode 100644
index 0000000..6a040ab
--- /dev/null
+++ b/AdvancedAPI.Data/Migrations/20240719221124_InitialCreate.cs
@@ -0,0 +1,221 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace AdvancedAPI.Data.Migrations
+{
+ public partial class InitialCreate : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "AspNetRoles",
+ columns: table => new
+ {
+ Id = table.Column(type: "nvarchar(450)", nullable: false),
+ Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoles", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUsers",
+ columns: table => new
+ {
+ Id = table.Column(type: "nvarchar(450)", nullable: false),
+ UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ EmailConfirmed = table.Column(type: "bit", nullable: false),
+ PasswordHash = table.Column(type: "nvarchar(max)", nullable: true),
+ SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true),
+ ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true),
+ PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true),
+ PhoneNumberConfirmed = table.Column(type: "bit", nullable: false),
+ TwoFactorEnabled = table.Column(type: "bit", nullable: false),
+ LockoutEnd = table.Column(type: "datetimeoffset", nullable: true),
+ LockoutEnabled = table.Column(type: "bit", nullable: false),
+ AccessFailedCount = table.Column(type: "int", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUsers", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetRoleClaims",
+ columns: table => new
+ {
+ Id = table.Column(type: "int", nullable: false)
+ .Annotation("SqlServer:Identity", "1, 1"),
+ RoleId = table.Column(type: "nvarchar(450)", nullable: false),
+ ClaimType = table.Column(type: "nvarchar(max)", nullable: true),
+ ClaimValue = table.Column(type: "nvarchar(max)", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserClaims",
+ columns: table => new
+ {
+ Id = table.Column(type: "int", nullable: false)
+ .Annotation("SqlServer:Identity", "1, 1"),
+ UserId = table.Column(type: "nvarchar(450)", nullable: false),
+ ClaimType = table.Column(type: "nvarchar(max)", nullable: true),
+ ClaimValue = table.Column(type: "nvarchar(max)", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetUserClaims_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserLogins",
+ columns: table => new
+ {
+ LoginProvider = table.Column(type: "nvarchar(450)", nullable: false),
+ ProviderKey = table.Column(type: "nvarchar(450)", nullable: false),
+ ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true),
+ UserId = table.Column(type: "nvarchar(450)", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
+ table.ForeignKey(
+ name: "FK_AspNetUserLogins_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserRoles",
+ columns: table => new
+ {
+ UserId = table.Column(type: "nvarchar(450)", nullable: false),
+ RoleId = table.Column(type: "nvarchar(450)", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserTokens",
+ columns: table => new
+ {
+ UserId = table.Column(type: "nvarchar(450)", nullable: false),
+ LoginProvider = table.Column(type: "nvarchar(450)", nullable: false),
+ Name = table.Column(type: "nvarchar(450)", nullable: false),
+ Value = table.Column(type: "nvarchar(max)", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
+ table.ForeignKey(
+ name: "FK_AspNetUserTokens_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetRoleClaims_RoleId",
+ table: "AspNetRoleClaims",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "RoleNameIndex",
+ table: "AspNetRoles",
+ column: "NormalizedName",
+ unique: true,
+ filter: "[NormalizedName] IS NOT NULL");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserClaims_UserId",
+ table: "AspNetUserClaims",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserLogins_UserId",
+ table: "AspNetUserLogins",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserRoles_RoleId",
+ table: "AspNetUserRoles",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "EmailIndex",
+ table: "AspNetUsers",
+ column: "NormalizedEmail");
+
+ migrationBuilder.CreateIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers",
+ column: "NormalizedUserName",
+ unique: true,
+ filter: "[NormalizedUserName] IS NOT NULL");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "AspNetRoleClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserLogins");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserTokens");
+
+ migrationBuilder.DropTable(
+ name: "AspNetRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUsers");
+ }
+ }
+}
diff --git a/AdvancedAPI.Data/Migrations/20240719221250_CreateIdentitySchema.Designer.cs b/AdvancedAPI.Data/Migrations/20240719221250_CreateIdentitySchema.Designer.cs
new file mode 100644
index 0000000..d13715a
--- /dev/null
+++ b/AdvancedAPI.Data/Migrations/20240719221250_CreateIdentitySchema.Designer.cs
@@ -0,0 +1,278 @@
+//
+using System;
+using AdvancedAPI.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace AdvancedAPI.Data.Migrations
+{
+ [DbContext(typeof(AdvancedApiContext))]
+ [Migration("20240719221250_CreateIdentitySchema")]
+ partial class CreateIdentitySchema
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.32")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex")
+ .HasFilter("[NormalizedName] IS NOT NULL");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1);
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("bit");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1);
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("RoleId")
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Value")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/AdvancedAPI.Data/Migrations/20240719221250_CreateIdentitySchema.cs b/AdvancedAPI.Data/Migrations/20240719221250_CreateIdentitySchema.cs
new file mode 100644
index 0000000..fe4e94e
--- /dev/null
+++ b/AdvancedAPI.Data/Migrations/20240719221250_CreateIdentitySchema.cs
@@ -0,0 +1,19 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace AdvancedAPI.Data.Migrations
+{
+ public partial class CreateIdentitySchema : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+
+ }
+ }
+}
diff --git a/AdvancedAPI.Data/Migrations/AdvancedApiContextModelSnapshot.cs b/AdvancedAPI.Data/Migrations/AdvancedApiContextModelSnapshot.cs
new file mode 100644
index 0000000..d27c4fd
--- /dev/null
+++ b/AdvancedAPI.Data/Migrations/AdvancedApiContextModelSnapshot.cs
@@ -0,0 +1,276 @@
+//
+using System;
+using AdvancedAPI.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace AdvancedAPI.Data.Migrations
+{
+ [DbContext(typeof(AdvancedApiContext))]
+ partial class AdvancedApiContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.32")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex")
+ .HasFilter("[NormalizedName] IS NOT NULL");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1);
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("bit");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1);
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("RoleId")
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Value")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/AdvancedAPI.Data/Program.cs b/AdvancedAPI.Data/Program.cs
index 8490520..8ddb4d3 100644
--- a/AdvancedAPI.Data/Program.cs
+++ b/AdvancedAPI.Data/Program.cs
@@ -1,16 +1,9 @@
using AdvancedAPI.Data;
-using Microsoft.EntityFrameworkCore;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(
(context, services) =>
{
- var connectionString = context.Configuration.GetConnectionString("DefaultConnection");
-
- // Register ApplicationDbContext with the connection string
- services.AddDbContext(options =>
- options.UseSqlServer(connectionString));
-
services.AddHostedService();
})
.Build();
diff --git a/AdvancedAPI.Data/Repositories/IdentityRepository.cs b/AdvancedAPI.Data/Repositories/IdentityRepository.cs
new file mode 100644
index 0000000..29a584a
--- /dev/null
+++ b/AdvancedAPI.Data/Repositories/IdentityRepository.cs
@@ -0,0 +1,25 @@
+using AdvancedAPI.Data.Repositories.Interfaces;
+using Microsoft.AspNetCore.Identity;
+
+namespace AdvancedAPI.Data.Repositories;
+
+///
+public class IdentityRepository : IIdentityRepository
+{
+ private readonly UserManager _userManager;
+
+ ///
+ /// Constructor.
+ ///
+ public IdentityRepository(UserManager userManager)
+ {
+ _userManager = userManager;
+ }
+
+ ///
+ public async Task GetUser(string userName) => await _userManager.FindByNameAsync(userName);
+
+ ///
+ public async Task CheckPassword(IdentityUser user, string password) =>
+ await _userManager.CheckPasswordAsync(user, password);
+}
diff --git a/AdvancedAPI.Data/Repositories/Interfaces/IIdentityRepository.cs b/AdvancedAPI.Data/Repositories/Interfaces/IIdentityRepository.cs
new file mode 100644
index 0000000..57758ea
--- /dev/null
+++ b/AdvancedAPI.Data/Repositories/Interfaces/IIdentityRepository.cs
@@ -0,0 +1,19 @@
+using Microsoft.AspNetCore.Identity;
+
+namespace AdvancedAPI.Data.Repositories.Interfaces;
+
+///
+/// Identity repository.
+///
+public interface IIdentityRepository
+{
+ ///
+ /// Getting the user from User manager.
+ ///
+ Task GetUser(string userName);
+
+ ///
+ /// Checks the password of the user with user manager.
+ ///
+ Task CheckPassword(IdentityUser user, string password);
+}
diff --git a/AdvancedAPI.Data/ViewModels/Authentication/LoginRequestModel.cs b/AdvancedAPI.Data/ViewModels/Authentication/LoginRequestModel.cs
new file mode 100644
index 0000000..375a8fa
--- /dev/null
+++ b/AdvancedAPI.Data/ViewModels/Authentication/LoginRequestModel.cs
@@ -0,0 +1,22 @@
+using Newtonsoft.Json;
+
+namespace AdvancedAPI.Data.ViewModels.Authentication;
+
+///
+/// Request model to login.
+///
+[JsonObject]
+public class LoginRequestModel
+{
+ ///
+ /// user name.
+ ///
+ [JsonProperty("User name")]
+ public string Username { get; set; }
+
+ ///
+ /// Password.
+ ///
+ [JsonProperty("Password")]
+ public string Password { get; set; }
+}
diff --git a/AdvancedAPI.Data/ViewModels/Houses/HouseResponseModel.cs b/AdvancedAPI.Data/ViewModels/Houses/HouseResponseModel.cs
index 00d7421..0d3db78 100644
--- a/AdvancedAPI.Data/ViewModels/Houses/HouseResponseModel.cs
+++ b/AdvancedAPI.Data/ViewModels/Houses/HouseResponseModel.cs
@@ -11,6 +11,6 @@ public class HouseResponseModel
///
/// Name of the street where the house is located.
///
- [JsonProperty("street Name")]
+ [JsonProperty("street name")]
public string StreetName { get; set; } = string.Empty;
}
diff --git a/AdvancedAPI.Data/appsettings.json b/AdvancedAPI.Data/appsettings.json
index 8f9438f..3a6e1a6 100644
--- a/AdvancedAPI.Data/appsettings.json
+++ b/AdvancedAPI.Data/appsettings.json
@@ -1,6 +1,6 @@
{
"ConnectionStrings": {
- "DefaultConnection": "Server=server;Database=database;User Id=username;Password=password;"
+ "DefaultConnection": "Server=DESKTOP-HFMAPSN\\SQLEXPRESS;Database=master;Trusted_Connection=True;Integrated Security=True;"
},
"Logging": {
"LogLevel": {
diff --git a/AdvancedAPI/BaseControllers/AdminBaseController.cs b/AdvancedAPI/BaseControllers/AdminBaseController.cs
new file mode 100644
index 0000000..10c38a9
--- /dev/null
+++ b/AdvancedAPI/BaseControllers/AdminBaseController.cs
@@ -0,0 +1,18 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace AdvancedAPI.BaseControllers;
+
+///
+/// Controller for admin related endpoints.
+///
+[Microsoft.AspNetCore.Components.Route("admin")]
+[ApiExplorerSettings(GroupName = "Admin")]
+public class AdminBaseController : BaseController
+{
+ ///
+ /// Constructor.
+ ///
+ public AdminBaseController()
+ {
+ }
+}
diff --git a/AdvancedAPI/BaseControllers/BaseController.cs b/AdvancedAPI/BaseControllers/BaseController.cs
new file mode 100644
index 0000000..0d1a026
--- /dev/null
+++ b/AdvancedAPI/BaseControllers/BaseController.cs
@@ -0,0 +1,58 @@
+using AdvancedAPI.Data.ViewModels;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AdvancedAPI.BaseControllers;
+
+///
+/// Base controller.
+///
+[ApiController]
+[Route("api")]
+public class BaseController : ControllerBase
+{
+ ///
+ /// Constructor.
+ ///
+ public BaseController()
+ {
+ }
+
+ ///
+ /// Returns an error when user is sending invalid request data.
+ ///
+ protected BadRequestObjectResult BadRequestResult(string? message)
+ {
+ return BadRequest(
+ new ErrorResponseModel
+ {
+ Code = 400,
+ Message = !string.IsNullOrEmpty(message) ? message : "The data you sent to the endpoint is invalid.",
+ });
+ }
+
+ ///
+ /// Returns an error when user is not authorized.
+ ///
+ protected UnauthorizedObjectResult UnauthorizedResult(string? message)
+ {
+ return Unauthorized(
+ new ErrorResponseModel
+ {
+ Code = 401,
+ Message = !string.IsNullOrEmpty(message) ? message : "You are not authorized for this request.",
+ });
+ }
+
+ ///
+ /// Returns an error when object is not found.
+ ///
+ protected NotFoundObjectResult NotFoundResult(string? message)
+ {
+ return NotFound(
+ new ErrorResponseModel
+ {
+ Code = 404,
+ Message = !string.IsNullOrEmpty(message) ? message : "Could not find any result",
+ });
+ }
+}
diff --git a/AdvancedAPI/Controllers/AuthenticationContoller.cs b/AdvancedAPI/Controllers/AuthenticationContoller.cs
new file mode 100644
index 0000000..b5a5f69
--- /dev/null
+++ b/AdvancedAPI/Controllers/AuthenticationContoller.cs
@@ -0,0 +1,47 @@
+using System.IdentityModel.Tokens.Jwt;
+using AdvancedAPI.BaseControllers;
+using AdvancedAPI.Data.ViewModels.Authentication;
+using Business.Services.Interfaces;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AdvancedAPI.Controllers;
+
+///
+/// Authentication endpoints.
+///
+[Microsoft.AspNetCore.Components.Route("authentication")]
+public class AuthenticationContoller : BaseController
+{
+ private readonly IAuthenticationService _authenticationService;
+ private readonly IConfiguration _configuration;
+
+ ///
+ /// Constructor.
+ ///
+ public AuthenticationContoller(IAuthenticationService authenticationService, IConfiguration configuration)
+ {
+ _authenticationService = authenticationService;
+ _configuration = configuration;
+ }
+
+ ///
+ /// endpoint to log in.
+ ///
+ [HttpPost]
+ [Route("login")]
+ public async Task Login([FromBody] LoginRequestModel model)
+ {
+ JwtSecurityToken? token = await _authenticationService.Login(model);
+
+ if (token != null)
+ {
+ return Ok(new
+ {
+ token = new JwtSecurityTokenHandler().WriteToken(token),
+ expiration = token.ValidTo,
+ });
+ }
+
+ return UnauthorizedResult(null);
+ }
+}
diff --git a/AdvancedAPI/Controllers/HouseController.cs b/AdvancedAPI/Controllers/HouseController.cs
index 65c4c14..356c4c5 100644
--- a/AdvancedAPI/Controllers/HouseController.cs
+++ b/AdvancedAPI/Controllers/HouseController.cs
@@ -1,4 +1,5 @@
-using AdvancedAPI.Data.ViewModels;
+using AdvancedAPI.BaseControllers;
+using AdvancedAPI.Data.ViewModels;
using AdvancedAPI.Data.ViewModels.Houses;
using Business.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
@@ -8,9 +9,8 @@ namespace AdvancedAPI.Controllers;
///
/// House api provided operation to main the houses.
///
-[ApiController]
-[Route("api/house")]
-public class HouseController : ControllerBase
+[Route("house")]
+public class HouseController : BaseController
{
private readonly IHouseService _houseService;
@@ -35,12 +35,7 @@ public async Task Overview(CancellationToken cancellationToken =
if (houseResponseModels == null || !houseResponseModels.Any())
{
- return NotFound(
- new ErrorResponseModel
- {
- Code = 404,
- Message = "No houses found",
- });
+ return NotFoundResult("Could not find any houses");
}
return Ok(new List());
diff --git a/AdvancedAPI/Program.cs b/AdvancedAPI/Program.cs
index 022cbea..da32119 100644
--- a/AdvancedAPI/Program.cs
+++ b/AdvancedAPI/Program.cs
@@ -1,25 +1,80 @@
using System.Reflection;
+using System.Text;
+using AdvancedAPI.Data;
+using AdvancedAPI.Data.Repositories;
+using AdvancedAPI.Data.Repositories.Interfaces;
+using Business.Services;
+using Business.Services.Interfaces;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
-builder.Services.AddSwaggerGen(c =>
-{
- c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
+// Services.
+builder.Services.AddScoped();
+
+// Repositories.
+builder.Services.AddScoped();
+
+builder.Services.AddSwaggerGen(
+ c =>
{
- Title = "AdvancedAPI",
- Version = "v1",
- Description = "The Advanced API of DustSwiffer",
+ c.SwaggerDoc(
+ "v1",
+ new Microsoft.OpenApi.Models.OpenApiInfo
+ {
+ Title = "AdvancedAPI",
+ Version = "v1",
+ Description = "The Advanced API of DustSwiffer",
+ });
+
+ var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
+ var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
+ c.IncludeXmlComments(xmlPath);
});
- var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
- var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
- c.IncludeXmlComments(xmlPath);
-});
+var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
+
+// Register ApplicationDbContext with the connection string
+builder.Services.AddDbContext(
+ options =>
+ options.UseSqlServer(connectionString));
+
+builder.Services.AddIdentity()
+ .AddEntityFrameworkStores()
+ .AddDefaultTokenProviders();
-var app = builder.Build();
+builder.Services.AddScoped();
+
+// Configure JWT authentication
+// Get the JWT secret key from configuration
+string jwtKey = builder.Configuration["Jwt:Key"];
+byte[] key = Encoding.ASCII.GetBytes(jwtKey);
+
+builder.Services.AddAuthentication(x =>
+ {
+ x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(x =>
+ {
+ x.RequireHttpsMetadata = false;
+ x.SaveToken = true;
+ x.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(key),
+ ValidateIssuer = false,
+ ValidateAudience = false,
+ };
+ });
+
+WebApplication app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
@@ -35,17 +90,35 @@
app.UseRouting();
+app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger();
-app.UseSwaggerUI(c =>
-{
- c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
- c.RoutePrefix = string.Empty; // Set Swagger UI at the app's root
-});
+app.UseSwaggerUI(
+ c =>
+ {
+ c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
+ c.RoutePrefix = string.Empty; // Set Swagger UI at the app's root
+ });
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
+// Seed the database with initial data
+using (IServiceScope scope = app.Services.CreateScope())
+{
+ IServiceProvider services = scope.ServiceProvider;
+
+ try
+ {
+ await DbInitializer.Initialize(services);
+ }
+ catch (Exception ex)
+ {
+ ILogger logger = services.GetRequiredService>();
+ logger.LogError(ex, "An error occurred seeding the DB.");
+ }
+}
+
app.Run();
diff --git a/AdvancedAPI/appsettings.json b/AdvancedAPI/appsettings.json
index 10f68b8..3d9ca42 100644
--- a/AdvancedAPI/appsettings.json
+++ b/AdvancedAPI/appsettings.json
@@ -1,4 +1,12 @@
{
+ "ConnectionStrings": {
+ "DefaultConnection": "Server=DESKTOP-HFMAPSN\\SQLEXPRESS;Database=master;Trusted_Connection=True;Integrated Security=True;"
+ },
+ "Jwt": {
+ "Key": "YourVeryLongAndSecureKeyForJWTAuthenticationWhichShouldBeAtLeast32Bytes",
+ "Issuer": "yourdomain.com",
+ "Audience": "yourdomain.com"
+ },
"Logging": {
"LogLevel": {
"Default": "Information",
diff --git a/AdvancedAPI/stylecop.json b/AdvancedAPI/stylecop.json
index b2f6c34..448e2ee 100644
--- a/AdvancedAPI/stylecop.json
+++ b/AdvancedAPI/stylecop.json
@@ -25,8 +25,7 @@
"systemUsingDirectivesFirst": true
},
"namingRules": {
- "allowCommonHungarianPrefixes": true,
-
+ "allowCommonHungarianPrefixes": true
},
"layoutRules": {
"newlineAtEndOfFile": "require"