From 9740d92cbf998044d56cb74dcd1c79f1ee89f490 Mon Sep 17 00:00:00 2001 From: sanchezvem Date: Sat, 21 Feb 2026 19:35:55 +0100 Subject: [PATCH] Created JWT endpoints --- BeReadyBackend/BeReadyBackend.csproj | 1 - BeReadyBackend/DTO/Auth/GetTokenDto.cs | 6 ++ BeReadyBackend/DTO/Auth/LoginDto.cs | 7 +++ BeReadyBackend/DTO/Auth/RefreshTokenDto.cs | 6 ++ .../Endpoints/Auth/LoginEndpoint.cs | 44 ++++++++++++++ .../Endpoints/Auth/RefreshTokenEndpoint.cs | 58 +++++++++++++++++++ BeReadyBackend/Program.cs | 12 ++-- .../Users/GetUserByUsernameSpec.cs | 13 +++++ 8 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 BeReadyBackend/DTO/Auth/GetTokenDto.cs create mode 100644 BeReadyBackend/DTO/Auth/LoginDto.cs create mode 100644 BeReadyBackend/DTO/Auth/RefreshTokenDto.cs create mode 100644 BeReadyBackend/Endpoints/Auth/LoginEndpoint.cs create mode 100644 BeReadyBackend/Endpoints/Auth/RefreshTokenEndpoint.cs create mode 100644 BeReadyBackend/Specifications/Users/GetUserByUsernameSpec.cs diff --git a/BeReadyBackend/BeReadyBackend.csproj b/BeReadyBackend/BeReadyBackend.csproj index 654e74a..21cffe3 100644 --- a/BeReadyBackend/BeReadyBackend.csproj +++ b/BeReadyBackend/BeReadyBackend.csproj @@ -27,7 +27,6 @@ - diff --git a/BeReadyBackend/DTO/Auth/GetTokenDto.cs b/BeReadyBackend/DTO/Auth/GetTokenDto.cs new file mode 100644 index 0000000..9600fea --- /dev/null +++ b/BeReadyBackend/DTO/Auth/GetTokenDto.cs @@ -0,0 +1,6 @@ +namespace BeReadyBackend.DTO.Auth; + +public class GetTokenDto +{ + public string? Token { get; set; } +} \ No newline at end of file diff --git a/BeReadyBackend/DTO/Auth/LoginDto.cs b/BeReadyBackend/DTO/Auth/LoginDto.cs new file mode 100644 index 0000000..a2b819b --- /dev/null +++ b/BeReadyBackend/DTO/Auth/LoginDto.cs @@ -0,0 +1,7 @@ +namespace BeReadyBackend.DTO.Auth; + +public class LoginDto +{ + public string? Username { get; set; } + public string? Password { get; set; } +} \ No newline at end of file diff --git a/BeReadyBackend/DTO/Auth/RefreshTokenDto.cs b/BeReadyBackend/DTO/Auth/RefreshTokenDto.cs new file mode 100644 index 0000000..d5c4d7e --- /dev/null +++ b/BeReadyBackend/DTO/Auth/RefreshTokenDto.cs @@ -0,0 +1,6 @@ +namespace BeReadyBackend.DTO.Auth; + +public class RefreshTokenDto +{ + public string? Token { get; set; } +} \ No newline at end of file diff --git a/BeReadyBackend/Endpoints/Auth/LoginEndpoint.cs b/BeReadyBackend/Endpoints/Auth/LoginEndpoint.cs new file mode 100644 index 0000000..7f05e5d --- /dev/null +++ b/BeReadyBackend/Endpoints/Auth/LoginEndpoint.cs @@ -0,0 +1,44 @@ +using BeReadyBackend.DTO.Auth; +using BeReadyBackend.Models; +using BeReadyBackend.Repositories; +using BeReadyBackend.Specifications.Users; +using FastEndpoints; +using FastEndpoints.Security; + +namespace BeReadyBackend.Endpoints.Auth; + +public class LoginEndpoint(UsersRepository usersRepository, AutoMapper.IMapper mapper) : Endpoint +{ + public override void Configure() + { + Post("/Auth/Login"); + AllowAnonymous(); + } + + public override async Task HandleAsync(LoginDto req, CancellationToken ct) + { + User? user = await usersRepository.SingleOrDefaultAsync(new GetUserByUsernameSpec(req.Username!), ct); + + if (user == null) + { + await Send.UnauthorizedAsync(ct); + return; + } + + if (BCrypt.Net.BCrypt.Verify(req.Password + user.Salt, user.Password)) + { + string jwtToken = JwtBearer.CreateToken(o => + { + o.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong"; + o.ExpireAt = DateTime.UtcNow.AddDays(15); + o.User.Claims.Add(("Username", user.Username)!); + o.User.Claims.Add(("FullName", user.FirstName + user.Name)); + o.User.Claims.Add(("UserId", user.Id.ToString())); + } + ); + + await Send.OkAsync(new GetTokenDto { Token = jwtToken }, ct); + } + else await Send.UnauthorizedAsync(ct); + } +} \ No newline at end of file diff --git a/BeReadyBackend/Endpoints/Auth/RefreshTokenEndpoint.cs b/BeReadyBackend/Endpoints/Auth/RefreshTokenEndpoint.cs new file mode 100644 index 0000000..1f1325e --- /dev/null +++ b/BeReadyBackend/Endpoints/Auth/RefreshTokenEndpoint.cs @@ -0,0 +1,58 @@ +using System.IdentityModel.Tokens.Jwt; +using BeReadyBackend.DTO.Auth; +using BeReadyBackend.Models; +using BeReadyBackend.Repositories; +using BeReadyBackend.Specifications.Users; +using FastEndpoints; +using FastEndpoints.Security; + + +namespace BeReadyBackend.Endpoints.Auth; + +public class RefreshTokenEndpoint(UsersRepository usersRepository, AutoMapper.IMapper mapper) : Endpoint +{ + public override void Configure() + { + Post("/Auth/RefreshToken"); + AllowAnonymous(); + } + + public override async Task HandleAsync(RefreshTokenDto req, CancellationToken ct) + { + try + { + JwtSecurityTokenHandler handler = new(); + JwtSecurityToken? token = handler.ReadJwtToken(req.Token); + string? username = token.Claims.FirstOrDefault(c => c.Type == "Username")?.Value; + + if (string.IsNullOrWhiteSpace(username)) + { + await Send.UnauthorizedAsync(ct); + return; + } + + User? user = await usersRepository.FirstOrDefaultAsync(new GetUserByUsernameSpec(username), ct); + if (user is null) + { + await Send.UnauthorizedAsync(ct); + return; + } + + string jwtToken = JwtBearer.CreateToken(o => + { + o.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong"; + o.ExpireAt = DateTime.UtcNow.AddDays(15); + o.User.Claims.Add(("Username", user.Username)!); + o.User.Claims.Add(("FullName", user.FirstName + user.Name)); + o.User.Claims.Add(("UserId", user.Id.ToString())); + } + ); + + await Send.OkAsync(new GetTokenDto { Token = jwtToken }, ct); + } + catch + { + await Send.UnauthorizedAsync(ct); + } + } +} \ No newline at end of file diff --git a/BeReadyBackend/Program.cs b/BeReadyBackend/Program.cs index e0d096a..660245c 100644 --- a/BeReadyBackend/Program.cs +++ b/BeReadyBackend/Program.cs @@ -15,10 +15,6 @@ builder.Services .AddAuthenticationJwtBearer(s => s.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong") .AddAuthorization() .AddFastEndpoints() - .SwaggerDocument(options => - { - options.ShortSchemaNames = true; - }) .AddCors(options => { options.AddDefaultPolicy(policyBuilder => @@ -29,6 +25,10 @@ builder.Services .AllowAnyHeader() .WithExposedHeaders(HeaderNames.ContentDisposition); }); + }) + .SwaggerDocument(options => + { + options.ShortSchemaNames = true; }); builder.Services.AddDbContext(); @@ -65,8 +65,8 @@ app.UseAuthentication() }) .UseSwaggerGen(); -app.UseHttpsRedirection(); +// app.UseHttpsRedirection(); -app.UseCors(); +// app.UseCors(); app.Run(); \ No newline at end of file diff --git a/BeReadyBackend/Specifications/Users/GetUserByUsernameSpec.cs b/BeReadyBackend/Specifications/Users/GetUserByUsernameSpec.cs new file mode 100644 index 0000000..a69ed13 --- /dev/null +++ b/BeReadyBackend/Specifications/Users/GetUserByUsernameSpec.cs @@ -0,0 +1,13 @@ +using Ardalis.Specification; +using BeReadyBackend.Models; + +namespace BeReadyBackend.Specifications.Users; + +public class GetUserByUsernameSpec : SingleResultSpecification +{ + public GetUserByUsernameSpec(string username) + { + Query + .Where(x => x.Username == username); + } +} \ No newline at end of file