diff --git a/BlazorPolicyAuth/BlazorPolicyAuth.csproj b/BlazorPolicyAuth/BlazorPolicyAuth.csproj
index b82a96e..1b2989d 100644
--- a/BlazorPolicyAuth/BlazorPolicyAuth.csproj
+++ b/BlazorPolicyAuth/BlazorPolicyAuth.csproj
@@ -19,6 +19,7 @@
+
diff --git a/BlazorPolicyAuth/Models/Entities/UserAccount.cs b/BlazorPolicyAuth/Models/Entities/UserAccount.cs
index 584701f..8504e39 100644
--- a/BlazorPolicyAuth/Models/Entities/UserAccount.cs
+++ b/BlazorPolicyAuth/Models/Entities/UserAccount.cs
@@ -18,4 +18,9 @@ public class UserAccount
[Column("password")]
[MaxLength(100)]
public string? Password { get; set; }
+
+ public byte[] PasswordHash { get; set; }
+ public byte[] PasswordSalt { get; set; }
+ public DateTime DateCreated { get; set; } = DateTime.Now;
+ public string Role { get; set; } = "User";
}
\ No newline at end of file
diff --git a/BlazorPolicyAuth/Models/ViewModels/LoginViewModel.cs b/BlazorPolicyAuth/Models/ViewModels/LoginViewModel.cs
deleted file mode 100644
index 24bce9d..0000000
--- a/BlazorPolicyAuth/Models/ViewModels/LoginViewModel.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace BlazorPolicyAuth.Models.ViewModels;
-
-public class LoginViewModel
-{
- public string? UserName { get; set; }
- public string? Password { get; set; }
-}
\ No newline at end of file
diff --git a/BlazorPolicyAuth/Models/ViewModels/ServiceResponse.cs b/BlazorPolicyAuth/Models/ViewModels/ServiceResponse.cs
new file mode 100644
index 0000000..bb171d3
--- /dev/null
+++ b/BlazorPolicyAuth/Models/ViewModels/ServiceResponse.cs
@@ -0,0 +1,9 @@
+namespace BlazorPolicyAuth.Models.ViewModels
+{
+ public class ServiceResponse
+ {
+ public T? Data { get; set; }
+ public bool Success { get; set; } = true;
+ public string Message { get; set; } = string.Empty;
+ }
+}
diff --git a/BlazorPolicyAuth/Models/ViewModels/UserChangePassword.cs b/BlazorPolicyAuth/Models/ViewModels/UserChangePassword.cs
new file mode 100644
index 0000000..f32f5ec
--- /dev/null
+++ b/BlazorPolicyAuth/Models/ViewModels/UserChangePassword.cs
@@ -0,0 +1,12 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace BlazorPolicyAuth.Models.ViewModels
+{
+ public class UserChangePassword
+ {
+ [Required, StringLength(100, MinimumLength = 5)]
+ public string Password { get; set; } = string.Empty;
+ [Compare("Password", ErrorMessage = "The password do not match")]
+ public string ConfirmPassword { get; set; } = string.Empty;
+ }
+}
diff --git a/BlazorPolicyAuth/Models/ViewModels/UserLogin.cs b/BlazorPolicyAuth/Models/ViewModels/UserLogin.cs
new file mode 100644
index 0000000..6c20e0a
--- /dev/null
+++ b/BlazorPolicyAuth/Models/ViewModels/UserLogin.cs
@@ -0,0 +1,11 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace BlazorPolicyAuth.Models.ViewModels;
+
+public class UserLogin
+{
+ [Required, EmailAddress]
+ public string UserName { get; set; } = string.Empty;
+ [Required]
+ public string Password { get; set; }
+}
\ No newline at end of file
diff --git a/BlazorPolicyAuth/Models/ViewModels/UserRegister.cs b/BlazorPolicyAuth/Models/ViewModels/UserRegister.cs
new file mode 100644
index 0000000..ec3dd3f
--- /dev/null
+++ b/BlazorPolicyAuth/Models/ViewModels/UserRegister.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace BlazorPolicyAuth.Models.ViewModels
+{
+ public class UserRegister
+ {
+ [Required, EmailAddress]
+ public string Email { get; set; } = string.Empty;
+ [Required, StringLength(100, MinimumLength = 5)]
+ public string Password { get; set; } = string.Empty;
+ [Compare("Password", ErrorMessage = "The password do not match.")]
+ public string ConfirmPassword { get; set; } = string.Empty;
+ }
+}
diff --git a/BlazorPolicyAuth/Services/AuthService/AuthService.cs b/BlazorPolicyAuth/Services/AuthService/AuthService.cs
new file mode 100644
index 0000000..6237c9e
--- /dev/null
+++ b/BlazorPolicyAuth/Services/AuthService/AuthService.cs
@@ -0,0 +1,140 @@
+
+using BlazorPolicyAuth.Data;
+using BlazorPolicyAuth.Models.Entities;
+using BlazorPolicyAuth.Models.ViewModels;
+using BlazorPolicyAuth.Services.AuthService;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace BlazorAuth.Server.Services.AuthService;
+
+public class AuthService : IAuthService
+{
+ private readonly AppDbContext _context;
+ private readonly IConfiguration _configuration;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public AuthService(AppDbContext context, IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
+ {
+ _context = context;
+ _configuration = configuration;
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ public async Task> Register(UserAccount user, string password)
+ {
+ if (await UserExists(user.UserName))
+ {
+ return new ServiceResponse { Success = false, Message = "User already exist." };
+ }
+
+ CreatePasswordHash(password, out byte[] passwordHash, out byte[] passwordSalt);
+
+ user.PasswordHash = passwordHash;
+ user.PasswordSalt = passwordSalt;
+
+ _context.UserAccounts.Add(user);
+ await _context.SaveChangesAsync();
+
+ return new ServiceResponse { Data = user.Id, Message = "Registration successful" };
+ }
+
+ public async Task UserExists(string email)
+ {
+ if (await _context.UserAccounts.AnyAsync(user => user.UserName.ToLower().Equals(email.ToLower())))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ public async Task> Login(string email, string password)
+ {
+ var response = new ServiceResponse();
+ var user = await _context.UserAccounts.FirstOrDefaultAsync(u => u.UserName.ToLower().Equals(email.ToLower()));
+ if (user == null)
+ {
+ response.Success = false;
+ response.Message = "User not found.";
+ }
+ else if (!VeriyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
+ {
+ response.Success = false;
+ response.Message = "Wrong password.";
+ }
+ else
+ {
+ response.Data = CreateToken(user);
+ }
+
+ return response;
+ }
+
+ public async Task> ChangePassword(int userId, string newPassword)
+ {
+ var user = await _context.UserAccounts.FindAsync(userId);
+ if (user == null)
+ {
+ return new ServiceResponse { Success = false, Message = "User not found." };
+ }
+
+ CreatePasswordHash(newPassword, out byte[] passwordHash, out byte[] passwordSalt);
+
+ user.PasswordHash = passwordHash;
+ user.PasswordSalt = passwordSalt;
+
+ await _context.SaveChangesAsync();
+
+ return new ServiceResponse { Data = true, Message = "Password has been changed." };
+ }
+
+ public int GetUserId() => int.Parse(_httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
+
+ public string GetUserEmail() => _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.Name);
+
+ public async Task GetUserByEmail(string email)
+ {
+ return await _context.UserAccounts.FirstOrDefaultAsync(u => u.UserName.Equals(email));
+ }
+
+ private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
+ {
+ using var hmac = new HMACSHA512();
+ passwordSalt = hmac.Key;
+ passwordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password));
+ }
+
+ private static bool VeriyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
+ {
+ using var hmac = new HMACSHA512(passwordSalt);
+ var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password));
+ return computedHash.SequenceEqual(passwordHash);
+ }
+
+ private string CreateToken(UserAccount user)
+ {
+ var claims = new List
+ {
+ new(ClaimTypes.NameIdentifier, user.Id.ToString()),
+ new(ClaimTypes.Name, user.UserName),
+ new(ClaimTypes.Role, user.Role)
+ };
+
+ var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("AppSettings:Token").Value));
+
+ var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
+
+ var token = new JwtSecurityToken(
+ claims: claims,
+ expires: DateTime.Now.AddDays(1),
+ signingCredentials: creds);
+
+ var jwt = new JwtSecurityTokenHandler().WriteToken(token);
+
+ return jwt;
+ }
+}
\ No newline at end of file
diff --git a/BlazorPolicyAuth/Services/AuthService/IAuthService.cs b/BlazorPolicyAuth/Services/AuthService/IAuthService.cs
new file mode 100644
index 0000000..ce36661
--- /dev/null
+++ b/BlazorPolicyAuth/Services/AuthService/IAuthService.cs
@@ -0,0 +1,15 @@
+using BlazorPolicyAuth.Models.Entities;
+using BlazorPolicyAuth.Models.ViewModels;
+
+namespace BlazorPolicyAuth.Services.AuthService;
+
+public interface IAuthService
+{
+ Task> Register(UserAccount user, string password);
+ Task UserExists(string email);
+ Task> Login(string email, string password);
+ Task> ChangePassword(int userId, string newPassword);
+ int GetUserId();
+ string GetUserEmail();
+ Task GetUserByEmail(string email);
+}
\ No newline at end of file