diff --git a/SurveyBackend/SurveyBackend.API/Controllers/AuthController.cs b/SurveyBackend/SurveyBackend.API/Controllers/AuthController.cs index b0b0442..93fb89a 100644 --- a/SurveyBackend/SurveyBackend.API/Controllers/AuthController.cs +++ b/SurveyBackend/SurveyBackend.API/Controllers/AuthController.cs @@ -8,7 +8,7 @@ namespace SurveyBackend.Controllers; public class AuthController : ControllerBase { [HttpPost("login")] - public async Task GetToken([FromBody] UserLoginDTO loginData) + public async Task GetToken([FromBody] UserLoginDto loginData) { return Ok(); } diff --git a/SurveyBackend/SurveyBackend.API/DTOs/UserLoginDTO.cs b/SurveyBackend/SurveyBackend.API/DTOs/UserLoginDto.cs similarity index 82% rename from SurveyBackend/SurveyBackend.API/DTOs/UserLoginDTO.cs rename to SurveyBackend/SurveyBackend.API/DTOs/UserLoginDto.cs index 9dc2510..8e58ee2 100644 --- a/SurveyBackend/SurveyBackend.API/DTOs/UserLoginDTO.cs +++ b/SurveyBackend/SurveyBackend.API/DTOs/UserLoginDto.cs @@ -1,6 +1,6 @@ namespace SurveyBackend.DTOs; -public record UserLoginDTO +public record UserLoginDto { public required string Email { get; set; } public required string Password { get; set; } diff --git a/SurveyBackend/SurveyBackend.API/DTOs/UserRegistrationDTO.cs b/SurveyBackend/SurveyBackend.API/DTOs/UserRegistrationDto.cs similarity index 87% rename from SurveyBackend/SurveyBackend.API/DTOs/UserRegistrationDTO.cs rename to SurveyBackend/SurveyBackend.API/DTOs/UserRegistrationDto.cs index b3013db..3c0808d 100644 --- a/SurveyBackend/SurveyBackend.API/DTOs/UserRegistrationDTO.cs +++ b/SurveyBackend/SurveyBackend.API/DTOs/UserRegistrationDto.cs @@ -1,6 +1,6 @@ namespace SurveyBackend.DTOs; -public record UserRegistrationDTO +public record UserRegistrationDto { public string Email { get; set; } public string Username { get; set; } diff --git a/SurveyBackend/SurveyBackend.API/Program.cs b/SurveyBackend/SurveyBackend.API/Program.cs index 8aa2aca..23fd2d7 100644 --- a/SurveyBackend/SurveyBackend.API/Program.cs +++ b/SurveyBackend/SurveyBackend.API/Program.cs @@ -1,6 +1,8 @@ -using Microsoft.AspNetCore.Identity; +using System.Text; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; -using SurveyBackend.Core.Models; +using Microsoft.IdentityModel.Tokens; +using SurveyBackend.Infrastructure; using SurveyBackend.Infrastructure.Data; namespace SurveyBackend; @@ -11,33 +13,44 @@ public class Program { var builder = WebApplication.CreateBuilder(args); - // Add services to the container. + AuthOptions.MakeOptions(builder.Configuration, Environment.GetEnvironmentVariable("JWT_SECRET_KEY")); + builder.Services.AddAuthorization(); - builder.Services.AddDbContext(options => + builder.Services.AddDbContext(options => { options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")); }); - builder.Services.AddIdentity>(options => { }) - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = AuthOptions.Issuer, + ValidAudience = AuthOptions.Audience, + IssuerSigningKey = AuthOptions.SymmetricSecurityKey + }; + }); builder.Services.AddControllers(); - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); - // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } + app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/SurveyBackend/SurveyBackend.API/SurveyBackend.API.csproj b/SurveyBackend/SurveyBackend.API/SurveyBackend.API.csproj index e11596b..3764109 100644 --- a/SurveyBackend/SurveyBackend.API/SurveyBackend.API.csproj +++ b/SurveyBackend/SurveyBackend.API/SurveyBackend.API.csproj @@ -8,6 +8,7 @@ + diff --git a/SurveyBackend/SurveyBackend.API/appsettings.Development.json b/SurveyBackend/SurveyBackend.API/appsettings.Development.json index c14bf00..bcc29ea 100644 --- a/SurveyBackend/SurveyBackend.API/appsettings.Development.json +++ b/SurveyBackend/SurveyBackend.API/appsettings.Development.json @@ -7,5 +7,11 @@ }, "ConnectionStrings": { "DefaultConnection": "Data Source=Application.db" + }, + "JwtSettings": { + "SecretKey": "sigma_super_secret_key_for_jwt_tokens_yo", + "Issuer": "SurveyBackend", + "Audience": "SurveyClient", + "ExpiresInMinutes": 600 } } diff --git a/SurveyBackend/SurveyBackend.API/appsettings.json b/SurveyBackend/SurveyBackend.API/appsettings.json index 19a74e6..3b14cf8 100644 --- a/SurveyBackend/SurveyBackend.API/appsettings.json +++ b/SurveyBackend/SurveyBackend.API/appsettings.json @@ -8,5 +8,11 @@ "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Data Source=Application.db" + }, + "JwtSettings": { + "SecretKey": "sigma_super_secret_key_for_jwt_tokens_yo_that_should_be_stored_in_ENV", + "Issuer": "SurveyBackend", + "Audience": "SurveyClient", + "ExpiresInMinutes": 600 } } diff --git a/SurveyBackend/SurveyBackend.Core/Models/User.cs b/SurveyBackend/SurveyBackend.Core/Models/User.cs index ab4dcbe..78f2286 100644 --- a/SurveyBackend/SurveyBackend.Core/Models/User.cs +++ b/SurveyBackend/SurveyBackend.Core/Models/User.cs @@ -2,10 +2,14 @@ using Microsoft.AspNetCore.Identity; namespace SurveyBackend.Core.Models; -public class User : IdentityUser +public class User { + public string Id { get; set; } + public string Email { get; set; } public string FirstName { get; set; } public string LastName { get; set; } + public string Password { get; set; } + public ICollection Groups { get; set; } } \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.Core/Services/IUserService.cs b/SurveyBackend/SurveyBackend.Core/Services/IUserService.cs new file mode 100644 index 0000000..7d97a29 --- /dev/null +++ b/SurveyBackend/SurveyBackend.Core/Services/IUserService.cs @@ -0,0 +1,6 @@ +namespace SurveyBackend.Core.Services; + +public interface IUserService +{ + +} \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.Core/SurveyBackend.Core.csproj b/SurveyBackend/SurveyBackend.Core/SurveyBackend.Core.csproj index 7bc422a..adbb79f 100644 --- a/SurveyBackend/SurveyBackend.Core/SurveyBackend.Core.csproj +++ b/SurveyBackend/SurveyBackend.Core/SurveyBackend.Core.csproj @@ -7,7 +7,6 @@ - diff --git a/SurveyBackend/SurveyBackend.Infrastructure/AuthOptions.cs b/SurveyBackend/SurveyBackend.Infrastructure/AuthOptions.cs new file mode 100644 index 0000000..cd379e8 --- /dev/null +++ b/SurveyBackend/SurveyBackend.Infrastructure/AuthOptions.cs @@ -0,0 +1,32 @@ +using System.Text; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; + +namespace SurveyBackend.Infrastructure; + +public static class AuthOptions +{ + public static string Issuer; + public static string Audience; + public static TimeSpan TokenLifetime; + private static string? SecurityKey { get; set; } + + public static SymmetricSecurityKey SymmetricSecurityKey + { + get + { + ArgumentNullException.ThrowIfNull(SecurityKey); + + return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurityKey)); + } + } + + public static void MakeOptions(IConfigurationManager configurationManager, string? securityKey = null) + { + var jwtSettings = configurationManager.GetSection("JwtSettings"); + Issuer = jwtSettings["Issuer"] ?? "DefaultIssuer"; + Audience = jwtSettings["Audience"] ?? "DefaultAudience"; + TokenLifetime = TimeSpan.FromMinutes(int.Parse(jwtSettings["TokenLifetime"] ?? "60")); + SecurityKey = securityKey ?? jwtSettings["SecretKey"]; + } +} \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.Infrastructure/Data/DataContext.cs b/SurveyBackend/SurveyBackend.Infrastructure/Data/ApplicationDbContext.cs similarity index 59% rename from SurveyBackend/SurveyBackend.Infrastructure/Data/DataContext.cs rename to SurveyBackend/SurveyBackend.Infrastructure/Data/ApplicationDbContext.cs index a44573f..fb500db 100644 --- a/SurveyBackend/SurveyBackend.Infrastructure/Data/DataContext.cs +++ b/SurveyBackend/SurveyBackend.Infrastructure/Data/ApplicationDbContext.cs @@ -5,12 +5,12 @@ using SurveyBackend.Core.Models; namespace SurveyBackend.Infrastructure.Data; -public class DataContext : IdentityDbContext, int> +public class ApplicationDbContext : DbContext { + public DbSet Users { get; set; } public DbSet Groups { get; set; } - public DataContext(DbContextOptions options) : base(options) + public ApplicationDbContext(DbContextOptions options) : base(options) { - Database.EnsureCreated(); } } \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.Infrastructure/Helpers/TokenHelper.cs b/SurveyBackend/SurveyBackend.Infrastructure/Helpers/TokenHelper.cs new file mode 100644 index 0000000..a52ae64 --- /dev/null +++ b/SurveyBackend/SurveyBackend.Infrastructure/Helpers/TokenHelper.cs @@ -0,0 +1,31 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using Microsoft.IdentityModel.Tokens; +using SurveyBackend.Core.Models; + +namespace SurveyBackend.Infrastructure.Helpers; + +public class TokenHelper +{ + public static string GetAuthToken(User user) + { + var userId = user.Id.ToString(); + + var claims = new List + { + new(ClaimTypes.NameIdentifier, userId) + }; + + var jwt = new JwtSecurityToken( + claims: claims, + issuer: AuthOptions.Issuer, + audience: AuthOptions.Audience, + expires: DateTime.UtcNow + AuthOptions.TokenLifetime, + signingCredentials: new SigningCredentials(AuthOptions.SymmetricSecurityKey, SecurityAlgorithms.HmacSha256) + ); + + var token = new JwtSecurityTokenHandler().WriteToken(jwt); + + return token; + } +} \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.Infrastructure/Repositories/UserRepository.cs b/SurveyBackend/SurveyBackend.Infrastructure/Repositories/UserRepository.cs index 306872e..bd1980d 100644 --- a/SurveyBackend/SurveyBackend.Infrastructure/Repositories/UserRepository.cs +++ b/SurveyBackend/SurveyBackend.Infrastructure/Repositories/UserRepository.cs @@ -7,9 +7,9 @@ namespace SurveyBackend.Infrastructure.Repositories; public class UserRepository : IUserRepository { - private readonly DataContext _context; + private readonly ApplicationDbContext _context; - public UserRepository(DataContext context) + public UserRepository(ApplicationDbContext context) { _context = context; } diff --git a/SurveyBackend/SurveyBackend.Infrastructure/Services/IPasswordHasher.cs b/SurveyBackend/SurveyBackend.Infrastructure/Services/IPasswordHasher.cs new file mode 100644 index 0000000..52d1427 --- /dev/null +++ b/SurveyBackend/SurveyBackend.Infrastructure/Services/IPasswordHasher.cs @@ -0,0 +1,7 @@ +namespace SurveyBackend.Infrastructure.Services; + +public interface IPasswordHasher +{ + public string HashPassword(string password); + public bool Verify(string password, string hashedPassword); +} \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.Infrastructure/Services/Sha256PasswordHasher.cs b/SurveyBackend/SurveyBackend.Infrastructure/Services/Sha256PasswordHasher.cs new file mode 100644 index 0000000..f513cff --- /dev/null +++ b/SurveyBackend/SurveyBackend.Infrastructure/Services/Sha256PasswordHasher.cs @@ -0,0 +1,61 @@ +using System.Security.Cryptography; + +namespace SurveyBackend.Infrastructure.Services; + +public class Sha256PasswordHasher : IPasswordHasher +{ + private const int RehashCount = 10000; + + private const int HashSizeBytes = 16; + + private const int SaltSizeBytes = 16; + + public string HashPassword(string password) + { + var saltBytes = RandomNumberGenerator.GetBytes(SaltSizeBytes); + + using var deriveBytes = GetDeriveBytes(password, saltBytes); + + var hashBytes = deriveBytes.GetBytes(HashSizeBytes); + + var fullHashBytes = new byte[SaltSizeBytes + HashSizeBytes]; + + Array.Copy(saltBytes, 0, fullHashBytes, 0, SaltSizeBytes); + Array.Copy(hashBytes, 0, fullHashBytes, SaltSizeBytes, HashSizeBytes); + + var hashedPassword = Convert.ToBase64String(fullHashBytes); + + return hashedPassword; + } + + public bool Verify(string password, string hashedPassword) + { + var fullHashBytes = Convert.FromBase64String(hashedPassword); + + var saltBytes = new byte[SaltSizeBytes]; + + Array.Copy(fullHashBytes, 0, saltBytes, 0, SaltSizeBytes); + + using var deriveBytes = GetDeriveBytes(password, saltBytes); + + var hashBytes = deriveBytes.GetBytes(HashSizeBytes); + + var isMatch = CompareHashes(hashBytes, fullHashBytes); + + return isMatch; + } + + private static bool CompareHashes(byte[] hashBytes, byte[] fullHashBytes) + { + for (var i = 0; i < HashSizeBytes; i++) + if (hashBytes[i] != fullHashBytes[i + SaltSizeBytes]) + return false; + + return true; + } + + private static Rfc2898DeriveBytes GetDeriveBytes(string password, byte[] saltBytes) + { + return new Rfc2898DeriveBytes(password, saltBytes, RehashCount, HashAlgorithmName.SHA256); + } +} \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.Infrastructure/SurveyBackend.Infrastructure.csproj b/SurveyBackend/SurveyBackend.Infrastructure/SurveyBackend.Infrastructure.csproj index eeefcab..ce7dbfc 100644 --- a/SurveyBackend/SurveyBackend.Infrastructure/SurveyBackend.Infrastructure.csproj +++ b/SurveyBackend/SurveyBackend.Infrastructure/SurveyBackend.Infrastructure.csproj @@ -8,6 +8,7 @@ +