diff --git a/Controllers/WeatherForecastController.cs b/Controllers/WeatherForecastController.cs index f886d77..0f6e918 100644 --- a/Controllers/WeatherForecastController.cs +++ b/Controllers/WeatherForecastController.cs @@ -1,21 +1,32 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; +using QuotifyBE.Controllers; +using QuotifyBE.DTOs; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; namespace QuotifyBE.Controllers { + [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { + private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger _logger; + private readonly IConfiguration _appsettings; - public WeatherForecastController(ILogger logger) + public WeatherForecastController(ILogger logger, IConfiguration appsettings) { _logger = logger; + _appsettings = appsettings; } [HttpGet(Name = "GetWeatherForecast")] @@ -29,5 +40,48 @@ namespace QuotifyBE.Controllers }) .ToArray(); } + + [HttpPost("login")] + public IActionResult Login([FromBody] UserLoginDTO user) + { + if (user.Email == "admin" && user.Password == "password") + { + var token = GenerateJwtToken(user.Email); + return Ok(new { token }); + } + return Unauthorized(); + } + + [HttpGet("some_values")] + [Authorize] + public IActionResult GetValues() + { + return Ok(new string[] { "value1", "value2" }); + } + + private string GenerateJwtToken(string username) + { + var claims = new[] + { + new Claim(JwtRegisteredClaimNames.Sub, username), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }; + + var key = new SymmetricSecurityKey( + Encoding.UTF8.GetBytes(_appsettings["JwtSecret"]!) + // won't be null here - otherwise Program.cs wouldn't start + ); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + issuer: _appsettings["DomainName"]!, + audience: _appsettings["DomainName"]!, + claims: claims, + expires: DateTime.Now.AddDays(7), + signingCredentials: creds + ); + + return new JwtSecurityTokenHandler().WriteToken(token); + } } } diff --git a/DTOs/UserLoginDTO.cs b/DTOs/UserLoginDTO.cs new file mode 100644 index 0000000..5d59091 --- /dev/null +++ b/DTOs/UserLoginDTO.cs @@ -0,0 +1,10 @@ +namespace QuotifyBE.DTOs; + +public record class UserLoginDTO +{ + required public string Email { get; set; } + required public string Password { get; set; } + + // public UserLoginDTO() { } + +}; diff --git a/Program.cs b/Program.cs index 5ecae42..007e5bd 100644 --- a/Program.cs +++ b/Program.cs @@ -1,6 +1,9 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; using QuotifyBE.Data; using QuotifyBE.Entities; +using System.Text; var builder = WebApplication.CreateBuilder(args); @@ -9,7 +12,34 @@ var connectionString = builder.Configuration.GetConnectionString("DefaultConnect builder.Services.AddDbContext(options => options.UseNpgsql(connectionString)); +var JwtSecret = builder.Configuration["JwtSecret"] + ?? throw new InvalidOperationException("JWT token secret is not configured!!! Please configure JwtSecret in appsettings.json!"); + +var DomainName = builder.Configuration["DomainName"] + ?? throw new InvalidOperationException("Domain name is not configured!!! Please configure DomainName in appsettings.json!"); + +// Configure JWT authentication +// https://medium.com/@solomongetachew112/jwt-authentication-in-net-8-a-complete-guide-for-secure-and-scalable-applications-6281e5e8667c +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = DomainName, + ValidAudience = DomainName, + IssuerSigningKey = new SymmetricSecurityKey( + Encoding.UTF8.GetBytes(JwtSecret) + ) + }; + }); + // Add services to the container. +builder.Services.AddAuthorization(); +builder.Services.AddSingleton(builder.Configuration); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle @@ -28,6 +58,7 @@ if (app.Environment.IsDevelopment()) app.UseHttpsRedirection(); +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/QuotifyBE.csproj b/QuotifyBE.csproj index 9ea07e8..00432b3 100644 --- a/QuotifyBE.csproj +++ b/QuotifyBE.csproj @@ -11,6 +11,7 @@ + @@ -27,4 +28,8 @@ + + + + diff --git a/appsettings.example.json b/appsettings.example.json index 4a5ad9e..24444ef 100644 --- a/appsettings.example.json +++ b/appsettings.example.json @@ -1,4 +1,6 @@ { + "JwtSecret": "this is a sample jwt secret token required for quotify - it needs to have at least 256 bits (32 bytes long)", + "DomainName": "example.com", "ConnectionStrings": { "DefaultConnection": "Server=server-host;Database=db-name;Username=quotify-user;Password=user-secret" },