From da27f7e8823d48b00b99d3af2addf3919443f5c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Szab=C3=B3?= Date: Tue, 11 Mar 2025 20:23:14 +0100 Subject: [PATCH] Middleware 1.0 --- src/WorkFlowCheck.Common/DTO/UserDTO.cs | 4 +- .../Middleware/JwtMiddleware.cs | 56 +++++++++++ .../Middleware/JwtService.cs | 97 +++++++++++++++++++ src/WorkFlowCheck.Web/Program.cs | 43 +++++++- .../WorkFlowCheck.Web.csproj | 2 + src/WorkFlowCheck.Web/appsettings.json | 6 ++ 6 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 src/WorkFlowCheck.Web/Middleware/JwtMiddleware.cs create mode 100644 src/WorkFlowCheck.Web/Middleware/JwtService.cs diff --git a/src/WorkFlowCheck.Common/DTO/UserDTO.cs b/src/WorkFlowCheck.Common/DTO/UserDTO.cs index 225104a..5a39d65 100644 --- a/src/WorkFlowCheck.Common/DTO/UserDTO.cs +++ b/src/WorkFlowCheck.Common/DTO/UserDTO.cs @@ -7,6 +7,8 @@ public string FirstName { get; set; } = null!; public string LastName { get; set; } = null!; public string UserName { get;set; } = null!; - public string Password { get; set; } + public string Password { get; set; } + public string JwtToken { get; set; } = null!; } + } diff --git a/src/WorkFlowCheck.Web/Middleware/JwtMiddleware.cs b/src/WorkFlowCheck.Web/Middleware/JwtMiddleware.cs new file mode 100644 index 0000000..bfd8e6e --- /dev/null +++ b/src/WorkFlowCheck.Web/Middleware/JwtMiddleware.cs @@ -0,0 +1,56 @@ +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace WorkFlowCheck.Web.Middleware +{ + public class JwtMiddleware + { + private readonly RequestDelegate _next; + private const string _secretKey = "super_secret_key"; + + public JwtMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext context) + { + var token = context.Request.Cookies["AuthToken"]; + + if (!string.IsNullOrEmpty(token) && ValidateToken(token, out var claims)) + { + context.User = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")); + } + + await _next(context); + } + + private bool ValidateToken(string token, out Claim[] claims) + { + claims = null; + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.UTF8.GetBytes(_secretKey); + + try + { + var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = false, + ValidateAudience = false, + ClockSkew = TimeSpan.Zero + }, out var validatedToken); + + claims = principal.Claims.ToArray(); + return true; + } + catch + { + return false; + } + } + } +} diff --git a/src/WorkFlowCheck.Web/Middleware/JwtService.cs b/src/WorkFlowCheck.Web/Middleware/JwtService.cs new file mode 100644 index 0000000..4a09d88 --- /dev/null +++ b/src/WorkFlowCheck.Web/Middleware/JwtService.cs @@ -0,0 +1,97 @@ +using Microsoft.AspNetCore.Identity.Data; +using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using WorkFlowCheck.Common.DTO; + +namespace WorkFlowCheck.Web.Middleware +{ + public class JwtService + { + + private const string SecretKey = "super_secret_key"; + public readonly IConfiguration _configuration; + private readonly HttpClient _httpClient; + + public JwtService(HttpClient httpClient,IConfiguration configuration) + { + _configuration = configuration; + _httpClient = httpClient; + } + + public async Task AuthenticateUserAsync(UserDTO userDTO) + { + var jwtSettings = _configuration.GetSection("Jwt"); + var secretKey = jwtSettings["SecretKey"]; + var issuer = jwtSettings["Issuer"]; + var audience = jwtSettings["Audience"]; + var tokenLifetime = int.Parse(jwtSettings["TokenLifetimeMinutes"]); + + var response = await Authenticate(userDTO.UserName, userDTO.Password); + if (response != null && response.IsSuccess) + { + + var claims = new[] + { + new Claim(ClaimTypes.Name, userDTO.UserName), + new Claim(ClaimTypes.Role, "User") + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + var token = new JwtSecurityToken( + issuer: issuer, + audience: audience, + claims: claims, + expires: DateTime.UtcNow.AddMinutes(tokenLifetime), + signingCredentials: creds); + + return new JwtSecurityTokenHandler().WriteToken(token); + } + return null; + } + private async Task> Authenticate(string username, string password) + { + try + { + // Az API végpont meghatározása + string endpoint = $"{_httpClient.BaseAddress}api/user/authenticate"; + + var userDTO = new UserDTO() + { + Email = "", + FirstName = "", + LastName = "", + Id = 0, + UserName = username, + Password = password, + }; + + // HTTP POST kérés küldése + + using (HttpResponseMessage httpResponseMessage = await _httpClient.PostAsJsonAsync(endpoint, userDTO)) + { + httpResponseMessage.EnsureSuccessStatusCode(); + + var jsonString = await httpResponseMessage.Content.ReadAsStringAsync(); + var response = JsonConvert.DeserializeObject>(jsonString); + + return response ?? new ApiResponseDTO + { + IsSuccess = false, + }; + } + } + catch (Exception ex) + { + // Hiba visszaadása + return new ApiResponseDTO + { + IsSuccess = false + }; + } + } + } +} diff --git a/src/WorkFlowCheck.Web/Program.cs b/src/WorkFlowCheck.Web/Program.cs index cbab3c2..9f54edb 100644 --- a/src/WorkFlowCheck.Web/Program.cs +++ b/src/WorkFlowCheck.Web/Program.cs @@ -1,4 +1,11 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; using Serilog; +using System.Text; +using WorkFlowCheck.Common.DTO; +using WorkFlowCheck.Web.Middleware; using WorkFlowCheck.Web.Services; using WorkFlowCheck.Web.Services.Interfaces; @@ -17,9 +24,26 @@ builder.Services.AddScoped(); builder.Host.UseSerilog(); // Add services to the container. builder.Services.AddRazorPages(); +builder.Services.AddSingleton(); +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.RequireHttpsMetadata = true; + options.SaveToken = true; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = "https://auth.example.com", + ValidAudience = "https://mywebapp.com", + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("super_secret_key")) + }; + }); var app = builder.Build(); - +app.UseMiddleware(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) @@ -34,8 +58,25 @@ app.UseStaticFiles(); app.UseRouting(); +app.UseAuthentication(); app.UseAuthorization(); +app.MapPost("/login", async ([FromBody] UserDTO userDTO, JwtService jwtService, HttpContext httpContext) => +{ + var token = await jwtService.AuthenticateUserAsync(userDTO); + if (token is null) return Results.Unauthorized(); + + httpContext.Response.Cookies.Append("AuthToken", token, new CookieOptions + { + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict + }); + return Results.Ok(new { Token = token }); +}); + +app.MapGet("/secure-data", [Authorize] () => "This is protected data"); + app.MapRazorPages(); app.Run(); diff --git a/src/WorkFlowCheck.Web/WorkFlowCheck.Web.csproj b/src/WorkFlowCheck.Web/WorkFlowCheck.Web.csproj index 398f9fb..4e75cbf 100644 --- a/src/WorkFlowCheck.Web/WorkFlowCheck.Web.csproj +++ b/src/WorkFlowCheck.Web/WorkFlowCheck.Web.csproj @@ -16,12 +16,14 @@ + + diff --git a/src/WorkFlowCheck.Web/appsettings.json b/src/WorkFlowCheck.Web/appsettings.json index 5e90cd1..33dd27a 100644 --- a/src/WorkFlowCheck.Web/appsettings.json +++ b/src/WorkFlowCheck.Web/appsettings.json @@ -1,6 +1,12 @@ { "ApiBaseUrl": "https://wfcapi.nuvolar.hu/", "ApiKey": "RUJeLSpSMzVASUdaRCEzUyYxRSE0VyFISFRSJC0zRzhLM1hCSDU=", + "Jwt": { + "SecretKey": "super_secret_key", + "Issuer": "https://wfcapi.nuvolar.hu/", + "Audience": "https://mywebapp.com", + "TokenLifetimeMinutes": 30 + }, "Logging": { "LogLevel": { "Default": "Information",