diff --git a/src/BHC24.Api/BHC24.Api.csproj b/src/BHC24.Api/BHC24.Api.csproj index d76c0c4..36963ff 100644 --- a/src/BHC24.Api/BHC24.Api.csproj +++ b/src/BHC24.Api/BHC24.Api.csproj @@ -7,6 +7,7 @@ + @@ -25,6 +26,7 @@ + diff --git a/src/BHC24.Api/Controllers/AuthController.cs b/src/BHC24.Api/Controllers/AuthController.cs new file mode 100644 index 0000000..e139f9e --- /dev/null +++ b/src/BHC24.Api/Controllers/AuthController.cs @@ -0,0 +1,70 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using BHC24.Api.Persistence.Models; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; +using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames; + +namespace BHC24.Api.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class AuthController : ControllerBase +{ + private readonly UserManager _userManager; + private readonly IConfiguration _configuration; + + public AuthController(UserManager userManager, IConfiguration configuration) + { + _userManager = userManager; + _configuration = configuration; + } + + [HttpPost("register")] + public async Task Register([FromBody] RegisterRequest request) + { + var user = new AppUser { UserName = request.Email, Email = request.Email }; + var result = await _userManager.CreateAsync(user, request.Password); + if (result.Succeeded) + { + return Ok(); + } + return BadRequest(result.Errors); + } + + [HttpPost("login")] + public async Task Login([FromBody] LoginRequest request) + { + var user = await _userManager.FindByEmailAsync(request.Email); + if (user != null && await _userManager.CheckPasswordAsync(user, request.Password)) + { + var token = GenerateJwtToken(user); + return Ok(new { Token = token }); + } + return Unauthorized(); + } + + private string GenerateJwtToken(AppUser user) + { + var claims = new[] + { + new Claim(JwtRegisteredClaimNames.Sub, user.UserName), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()) + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + issuer: _configuration["Jwt:Issuer"], + audience: _configuration["Jwt:Audience"], + claims: claims, + expires: DateTime.Now.AddDays(1), + signingCredentials: creds); + + return new JwtSecurityTokenHandler().WriteToken(token); + } +} \ No newline at end of file diff --git a/src/BHC24.Api/Models/LoginRequest.cs b/src/BHC24.Api/Models/LoginRequest.cs new file mode 100644 index 0000000..1af4e7f --- /dev/null +++ b/src/BHC24.Api/Models/LoginRequest.cs @@ -0,0 +1,7 @@ +namespace BHC24.Api.Controllers; + +public class LoginRequest +{ + public string Email { get; set; } + public string Password { get; set; } +} \ No newline at end of file diff --git a/src/BHC24.Api/Models/RegisterRequest.cs b/src/BHC24.Api/Models/RegisterRequest.cs new file mode 100644 index 0000000..17aca48 --- /dev/null +++ b/src/BHC24.Api/Models/RegisterRequest.cs @@ -0,0 +1,7 @@ +namespace BHC24.Api.Controllers; + +public class RegisterRequest +{ + public string Email { get; set; } + public string Password { get; set; } +} \ No newline at end of file diff --git a/src/BHC24.Api/Program.cs b/src/BHC24.Api/Program.cs index 4d805ed..3ef705e 100644 --- a/src/BHC24.Api/Program.cs +++ b/src/BHC24.Api/Program.cs @@ -1,8 +1,13 @@ +using System.Text; using BHC24.Api.Extensions; using BHC24.Api.Persistence; using BHC24.Api.Persistence.Models; +using BHC24.Api.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; using Serilog; var builder = WebApplication.CreateBuilder(args); @@ -11,7 +16,35 @@ // Add services to the container. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Open Project Platform", Version = "v1" }); + + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please enter a valid token", + Name = "Authorization", + Type = SecuritySchemeType.Http, + Scheme = "bearer", + BearerFormat = "JWT" + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] { } + } + }); +}); builder.Services.AddDbContext(); @@ -35,6 +68,32 @@ .AllowAnyHeader(); })); +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 = builder.Configuration["Jwt:Issuer"], + ValidAudience = builder.Configuration["Jwt:Audience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) + }; + }); + +builder.Services.AddHttpContextAccessor(); +builder.Services.AddAuthorization(); + + +builder.Services + .AddScoped(); + builder.Services.AddControllers(); builder.Services.AddAntiforgery(); @@ -55,12 +114,12 @@ app.Map("/", () => Results.Redirect("/swagger")); app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); + app.UseAntiforgery(); app.UseCors("MyPolicy"); -app.UseEndpoints(endpoints => -{ - endpoints.MapControllers(); -}); +app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); using var scope = app.Services.CreateScope(); var context = scope.ServiceProvider.GetService(); diff --git a/src/BHC24.Api/Services/AuthUserProvider.cs b/src/BHC24.Api/Services/AuthUserProvider.cs new file mode 100644 index 0000000..ce39061 --- /dev/null +++ b/src/BHC24.Api/Services/AuthUserProvider.cs @@ -0,0 +1,34 @@ +using System.Security.Claims; +using BHC24.Api.Persistence.Models; +using Microsoft.AspNetCore.Identity; + +namespace BHC24.Api.Services; + +public class AuthUserProvider +{ + private readonly UserManager _userManager; + private readonly IHttpContextAccessor _httpContextAccessor; + + public AuthUserProvider(UserManager userManager, IHttpContextAccessor httpContextAccessor) + { + _userManager = userManager; + _httpContextAccessor = httpContextAccessor; + } + + public async Task GetAsync() + { + string? userEmail = _httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(userEmail)) + { + throw new InvalidOperationException("User is not authenticated"); + } + + AppUser? user = await _userManager.FindByEmailAsync(userEmail); + if (user == null) + { + throw new InvalidOperationException("User not found"); + } + + return user; + } +} \ No newline at end of file diff --git a/src/BHC24.Api/appsettings.json b/src/BHC24.Api/appsettings.json index 10f68b8..188092d 100644 --- a/src/BHC24.Api/appsettings.json +++ b/src/BHC24.Api/appsettings.json @@ -5,5 +5,10 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Jwt": { + "Key": "MIHcAgEBBEIBFAXHNII96JvdSLTE9TVt0OTV3GQTTJPWX82FcjglFtZDGmRX6e+y\nzJlEGJkw32s4V4eEYnMgpR7rZ59Acnwt2CegBwYFK4EEACOhgYkDgYYABAGeOCFo\nGy0W4mMZWH402I8CVPGbzjoFz88cv6I2HQQGiVtnaXQcuERUuC1+e0SfDqNuYfCt\nv45wTwAttS0cLT1jbQGUPmSljatd/TO6oQbRgB8ko8b/09pWjCCylz99GN1tzam4\n5D8F2J4ePFHLFE59cpMRXkv0aRAHRetGIt9rAF/1lg==", + "Issuer": "YourIssuer", + "Audience": "YourAudience" + } }