From fd6860d102b9aa9eee1e543e37d2dba91ec106a7 Mon Sep 17 00:00:00 2001 From: Charles Le Maux Date: Thu, 3 Oct 2024 22:14:04 +0200 Subject: [PATCH] {V0.4} Added Authentication, easily settable for all routes. --- Controllers/Default.cs | 119 +++++++++++++++++++++++++++++++++++++++++ Controllers/Delete.cs | 6 +++ Controllers/Get.cs | 6 +++ Controllers/Post.cs | 6 +++ Controllers/Put.cs | 6 +++ Program.cs | 67 +++++++++++++++-------- Simple API.csproj | 1 + Simple API.http | 4 ++ appsettings.json | 7 ++- 9 files changed, 198 insertions(+), 24 deletions(-) create mode 100644 Controllers/Default.cs create mode 100644 Controllers/Delete.cs create mode 100644 Controllers/Get.cs create mode 100644 Controllers/Post.cs create mode 100644 Controllers/Put.cs diff --git a/Controllers/Default.cs b/Controllers/Default.cs new file mode 100644 index 0000000..67db814 --- /dev/null +++ b/Controllers/Default.cs @@ -0,0 +1,119 @@ +using System.ComponentModel.DataAnnotations; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; + + +namespace Simple_API.Controllers +{ + + [Route("auth/")] + [ApiController] + public class Default(IConfiguration configuration) : ControllerBase + { + + + public class AuthPayload + { + [DataType(DataType.EmailAddress)] + [EmailAddress(ErrorMessage = "Invalid Email Address.")] + [Required(ErrorMessage = "Email address is required.")] + public string? Email { get; init; } = string.Empty; + + + [DataType(DataType.Password)] + [Required(ErrorMessage = "Password is required.")] + [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$", + ErrorMessage = "Password must be at least 8 characters long and contain at least one uppercase letter," + + " one lowercase letter, one number, and one special character.")] + public string? Password { get; init; } = string.Empty; + } + + [HttpPut("register")] + public IActionResult Register([FromBody] AuthPayload authPayload) + { + return Ok(); + } + + [HttpPost("login")] + public IActionResult Login([FromBody] AuthPayload authPayload) + { + // Here, you would typically validate the user's credentials against a database. + if (authPayload.Email == "test@example.com" && authPayload.Password == "Password123!") + { + var claims = new[] + { + new Claim(ClaimTypes.Email, authPayload.Email), + new Claim(ClaimTypes.Role, "Admin"), + new Claim(ClaimTypes.GivenName, "Test_ID"), + }; + + var configKey = configuration["Jwt:Key"]; + + if (string.IsNullOrEmpty(configKey)) + { + return StatusCode(500); + } + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configKey)); + var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + issuer: configuration["Jwt:Issuer"], + audience: configuration["Jwt:Audience"], + claims: claims, + expires: DateTime.Now.AddMinutes(190), + signingCredentials: credentials); + + return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) }); + } + + return Unauthorized(); + } + } + + [Route("test/")] + [ApiController] + public class Test : ControllerBase + { + public class TestPayload + { + [Required(ErrorMessage = "Data field is required.")] + public string? Data { get; init; } = string.Empty; + } + + private const string ProtocolOk = "Protocol tested successfully."; + + // GET: test/get + [Authorize] + [HttpGet("get")] + public IActionResult TestGet() + { + return Ok($"GET: {ProtocolOk}"); + } + + // POST: test/post + [HttpPost("post")] + public IActionResult TestPost([FromBody] TestPayload testPayload) + { + return Ok($"POST: {ProtocolOk} Received: {testPayload.Data}"); + } + + // PUT: test/put + [HttpPut("put")] + public IActionResult TestPut([FromBody] TestPayload testPayload) + { + return Ok($"PUT: {ProtocolOk} Updated: {testPayload.Data}"); + } + + // DELETE: test/delete + [HttpDelete("delete")] + public IActionResult TestDelete([FromBody] TestPayload testPayload) + { + return Ok($"DELETE: {ProtocolOk} Deleted: {testPayload.Data}"); + } + } +} \ No newline at end of file diff --git a/Controllers/Delete.cs b/Controllers/Delete.cs new file mode 100644 index 0000000..d10790c --- /dev/null +++ b/Controllers/Delete.cs @@ -0,0 +1,6 @@ +namespace Simple_API.Controllers; + +public class Delete +{ + +} \ No newline at end of file diff --git a/Controllers/Get.cs b/Controllers/Get.cs new file mode 100644 index 0000000..f70b737 --- /dev/null +++ b/Controllers/Get.cs @@ -0,0 +1,6 @@ +namespace Simple_API.Controllers; + +public class Get +{ + +} \ No newline at end of file diff --git a/Controllers/Post.cs b/Controllers/Post.cs new file mode 100644 index 0000000..55942d1 --- /dev/null +++ b/Controllers/Post.cs @@ -0,0 +1,6 @@ +namespace Simple_API.Controllers; + +public class Post +{ + +} \ No newline at end of file diff --git a/Controllers/Put.cs b/Controllers/Put.cs new file mode 100644 index 0000000..7903c13 --- /dev/null +++ b/Controllers/Put.cs @@ -0,0 +1,6 @@ +namespace Simple_API.Controllers; + +public class Put +{ + +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index 3a1cbc1..944f553 100644 --- a/Program.cs +++ b/Program.cs @@ -1,8 +1,15 @@ +using System.Text; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; + +//Builder configuration var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.AddControllers(); +builder.Services.AddControllersWithViews(); builder.Services.AddCors(options => { options.AddPolicy("AllowAllOrigins", corsBuilder => @@ -13,9 +20,44 @@ builder.Services.AddCors(options => }); }); + +// JWT Configuration +var jwtSettings = builder.Configuration.GetSection("Jwt"); +var key = jwtSettings["Key"]; +var issuer = jwtSettings["Issuer"]; +var audience = jwtSettings["Audience"]; +if (string.IsNullOrEmpty(key)) +{ + return; +} +builder.Services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = issuer, + ValidAudience = audience, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) + }; + }); + + +// Build var app = builder.Build(); + +// App configuration +app.MapControllers(); + if (app.Environment.IsDevelopment()) { app.UseSwagger(); @@ -23,31 +65,10 @@ if (app.Environment.IsDevelopment()) } app.UseHttpsRedirection(); +app.UseAuthentication(); +app.UseAuthorization(); app.UseCors("AllowAllOrigins"); -var summaries = new[] -{ - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" -}; -app.MapGet("/weatherforecast", () => - { - var forecast = Enumerable.Range(1, 5).Select(index => - new WeatherForecast - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - Random.Shared.Next(-20, 55), - summaries[Random.Shared.Next(summaries.Length)] - )) - .ToArray(); - return forecast; - }) - .WithName("GetWeatherForecast") - .WithOpenApi(); app.Run(); - -internal record WeatherForecast (DateOnly Date, int TemperatureC, string? Summary) -{ - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -} \ No newline at end of file diff --git a/Simple API.csproj b/Simple API.csproj index 79b6e71..2c0f752 100644 --- a/Simple API.csproj +++ b/Simple API.csproj @@ -8,6 +8,7 @@ + diff --git a/Simple API.http b/Simple API.http index 4907fd5..51cc114 100644 --- a/Simple API.http +++ b/Simple API.http @@ -4,3 +4,7 @@ GET {{Simple_API_HostAddress}}/weatherforecast/ Accept: application/json ### + +GET {{Simple_API_HostAddress}}/test/get +Authorization: Bearer 1 +### \ No newline at end of file diff --git a/appsettings.json b/appsettings.json index 10f68b8..91e11c1 100644 --- a/appsettings.json +++ b/appsettings.json @@ -5,5 +5,10 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Jwt": { + "Key": "9831A382FD7395DD4D4F64B554962^&6@Vw1qR!Lg$+Pz", + "Issuer": "HubHarmonyApiTemplate", + "Audience": "HubHarmonyApiTemplate" + } }