diff --git a/MainProject.Tests/JsonData/appsettings.json b/MainProject.Tests/JsonData/appsettings.json index 80450dd..ec7201c 100644 --- a/MainProject.Tests/JsonData/appsettings.json +++ b/MainProject.Tests/JsonData/appsettings.json @@ -28,7 +28,7 @@ "Microsoft.AspNetCore": "Warning" } }, - "JWTSettings": { + "JwtSettings": { "ValidAudience": "http://localhost:4200", "ValidIssuer": "http://localhost:5000", "Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr", diff --git a/MainProject.Tests/JsonData/invalidCryptAppsettings.json b/MainProject.Tests/JsonData/invalidCryptAppsettings.json index 3c2dc8e..6e1ad5d 100644 --- a/MainProject.Tests/JsonData/invalidCryptAppsettings.json +++ b/MainProject.Tests/JsonData/invalidCryptAppsettings.json @@ -28,7 +28,7 @@ "Microsoft.AspNetCore": "Warning" } }, - "JWTSettings": { + "JwtSettings": { "ValidAudience": "http://localhost:4200", "ValidIssuer": "http://localhost:5000", "Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr", diff --git a/MainProject/Core/Attributes/JwtAuthorizationAttribute .cs b/MainProject/Core/Attributes/JwtAuthorizationAttribute .cs index 2a7121f..f23ebae 100644 --- a/MainProject/Core/Attributes/JwtAuthorizationAttribute .cs +++ b/MainProject/Core/Attributes/JwtAuthorizationAttribute .cs @@ -8,6 +8,7 @@ using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Text; using Microsoft.AspNetCore.Authorization; +using BasicDotnetTemplate.MainProject.Models.Settings; namespace BasicDotnetTemplate.MainProject.Core.Attributes { @@ -24,9 +25,66 @@ namespace BasicDotnetTemplate.MainProject.Core.Attributes public void OnAuthorization(AuthorizationFilterContext context) { - return; + // If [AllowAnonymous], skip + if (context.ActionDescriptor.EndpointMetadata.Any(em => em is AllowAnonymousAttribute)) + { + return; + } + var configuration = context.HttpContext.RequestServices.GetRequiredService(); + var appSettings = new AppSettings(); + configuration.GetSection("AppSettings").Bind(appSettings); + var jwtKey = appSettings?.JwtSettings?.Secret ?? String.Empty; + var jwtIssuer = appSettings?.JwtSettings?.ValidIssuer ?? String.Empty; + var jwtAudience = appSettings?.JwtSettings?.ValidAudience ?? String.Empty; + if (string.IsNullOrEmpty(jwtKey) || string.IsNullOrEmpty(jwtIssuer) || string.IsNullOrEmpty(jwtAudience)) + { + context.Result = new UnauthorizedResult(); + return; + } + + var token = context.HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); + + if (token == null) + { + context.Result = new UnauthorizedResult(); + return; + } + + try + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(jwtKey); + tokenHandler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = true, + ValidIssuer = jwtIssuer, + ValidateAudience = true, + ValidAudience = jwtAudience, + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero + }, out SecurityToken validatedToken); + + var jwtToken = (JwtSecurityToken)validatedToken; + + if (_policyName != null) + { + var claim = jwtToken.Claims.FirstOrDefault(c => c.Type == _policyName); + if (claim == null) + { + context.Result = new ForbidResult(); + return; + } + } + + } + catch + { + context.Result = new UnauthorizedResult(); + } } } } diff --git a/MainProject/Models/Api/Common/User/AuthenticatedUser.cs b/MainProject/Models/Api/Common/User/AuthenticatedUser.cs index 9ce6669..946f84e 100644 --- a/MainProject/Models/Api/Common/User/AuthenticatedUser.cs +++ b/MainProject/Models/Api/Common/User/AuthenticatedUser.cs @@ -1,4 +1,5 @@ using BasicDotnetTemplate.MainProject.Models.Api.Common.Role; +using DatabaseSqlServer = BasicDotnetTemplate.MainProject.Models.Database.SqlServer; namespace BasicDotnetTemplate.MainProject.Models.Api.Common.User; @@ -12,6 +13,16 @@ public class AuthenticatedUser public string? Email { get; set; } public UserRole? Role { get; set; } #nullable disable + + public AuthenticatedUser(DatabaseSqlServer.User user) + { + Guid = user.Guid; + Username = user.Username; + FirstName = user.FirstName; + LastName = user.LastName; + Email = user.Email; + Role = new UserRole(); + } } diff --git a/MainProject/Models/Database/SqlServer/Base.cs b/MainProject/Models/Database/SqlServer/Base.cs index c80e49b..6c89599 100644 --- a/MainProject/Models/Database/SqlServer/Base.cs +++ b/MainProject/Models/Database/SqlServer/Base.cs @@ -4,6 +4,7 @@ namespace BasicDotnetTemplate.MainProject.Models.Database.SqlServer { public int Id { get; set; } public string Guid { get; set; } + public bool IsDeleted { get; set; } public DateTime CreationTime { get; set; } public int CreationUserId { get; set; } public DateTime UpdateTime { get; set; } diff --git a/MainProject/Models/Settings/AppSettings.cs b/MainProject/Models/Settings/AppSettings.cs index a804c8e..b92311f 100644 --- a/MainProject/Models/Settings/AppSettings.cs +++ b/MainProject/Models/Settings/AppSettings.cs @@ -7,7 +7,7 @@ public class AppSettings public PrivateSettings? PrivateSettings { get; set; } public OpenApiSettings? OpenApiSettings { get; set; } public DatabaseSettings? DatabaseSettings { get; set; } - public JWTSettings? JWTSettings { get; set; } + public JwtSettings? JwtSettings { get; set; } public EncryptionSettings? EncryptionSettings { get; set; } #nullable disable diff --git a/MainProject/Models/Settings/JWTSettings.cs b/MainProject/Models/Settings/JwtSettings.cs similarity index 92% rename from MainProject/Models/Settings/JWTSettings.cs rename to MainProject/Models/Settings/JwtSettings.cs index f6b9deb..23f3690 100644 --- a/MainProject/Models/Settings/JWTSettings.cs +++ b/MainProject/Models/Settings/JwtSettings.cs @@ -1,6 +1,6 @@ namespace BasicDotnetTemplate.MainProject.Models.Settings; -public class JWTSettings +public class JwtSettings { #nullable enable public string? ValidAudience { get; set; } diff --git a/MainProject/Services/AuthService.cs b/MainProject/Services/AuthService.cs index b2b68f1..9149ac9 100644 --- a/MainProject/Services/AuthService.cs +++ b/MainProject/Services/AuthService.cs @@ -1,6 +1,7 @@ using BasicDotnetTemplate.MainProject.Models.Api.Data.Auth; using BasicDotnetTemplate.MainProject.Models.Api.Common.User; +using BasicDotnetTemplate.MainProject.Core.Database; using BasicDotnetTemplate.MainProject.Utils; namespace BasicDotnetTemplate.MainProject.Services; @@ -13,12 +14,16 @@ public interface IAuthService public class AuthService : BaseService, IAuthService { protected CryptUtils _cryptUtils; + protected readonly IUserService _userService; public AuthService( - IConfiguration configuration - ) : base(configuration) + IConfiguration configuration, + SqlServerContext sqlServerContext, + IUserService userService + ) : base(configuration, sqlServerContext) { _cryptUtils = new CryptUtils(_appSettings); + _userService = userService; } public async Task AuthenticateAsync(AuthenticateRequestData data) @@ -29,7 +34,11 @@ public class AuthService : BaseService, IAuthService if (!String.IsNullOrEmpty(decryptedUsername) && !String.IsNullOrEmpty(decryptedPassword)) { - + var user = await this._userService.GetUserByUsernameAndPassword(decryptedUsername, decryptedPassword); + if (user != null) + { + authenticatedUser = new AuthenticatedUser(user); + } } return authenticatedUser; diff --git a/MainProject/Services/BaseService.cs b/MainProject/Services/BaseService.cs index 3930d6b..409edc1 100644 --- a/MainProject/Services/BaseService.cs +++ b/MainProject/Services/BaseService.cs @@ -1,10 +1,4 @@ -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; +using BasicDotnetTemplate.MainProject.Core.Database; using BasicDotnetTemplate.MainProject.Models.Settings; namespace BasicDotnetTemplate.MainProject.Services; @@ -13,12 +7,17 @@ public class BaseService { protected readonly IConfiguration _configuration; protected readonly AppSettings _appSettings; + protected readonly SqlServerContext _sqlServerContext; - public BaseService(IConfiguration configuration) + public BaseService( + IConfiguration configuration, + SqlServerContext sqlServerContext + ) { _configuration = configuration; _appSettings = new AppSettings(); _configuration.GetSection("AppSettings").Bind(_appSettings); + _sqlServerContext = sqlServerContext; } } diff --git a/MainProject/Services/JwtService.cs b/MainProject/Services/JwtService.cs index cf6f561..fb962cc 100644 --- a/MainProject/Services/JwtService.cs +++ b/MainProject/Services/JwtService.cs @@ -4,12 +4,13 @@ using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; +using BasicDotnetTemplate.MainProject.Core.Database; namespace BasicDotnetTemplate.MainProject.Services; public interface IJwtService { - + } public class JwtService : BaseService, IJwtService @@ -19,14 +20,38 @@ public class JwtService : BaseService, IJwtService private readonly string _jwtAudience; public JwtService( - IConfiguration configuration - ) : base(configuration) + IConfiguration configuration, + SqlServerContext sqlServerContext + ) : base(configuration, sqlServerContext) { - _jwtKey = _appSettings?.JWTSettings?.Secret ?? String.Empty; - _jwtIssuer = _appSettings?.JWTSettings?.ValidIssuer ?? String.Empty; - _jwtAudience = _appSettings?.JWTSettings?.ValidAudience ?? String.Empty; + _jwtKey = _appSettings?.JwtSettings?.Secret ?? String.Empty; + _jwtIssuer = _appSettings?.JwtSettings?.ValidIssuer ?? String.Empty; + _jwtAudience = _appSettings?.JwtSettings?.ValidAudience ?? String.Empty; + } + + + public string GenerateToken(string userId, string username) + { + var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtKey)); + var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + var expiration = _appSettings?.JwtSettings?.ExpiredAfterMinsOfInactivity ?? 15; + + var claims = new List + { + new Claim(JwtRegisteredClaimNames.Sub, userId), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim("userid", userId) + }; + + var token = new JwtSecurityToken( + _jwtIssuer, + _jwtAudience, + claims, + expires: DateTime.Now.AddMinutes(expiration), + signingCredentials: credentials); + + return new JwtSecurityTokenHandler().WriteToken(token); } - } diff --git a/MainProject/Services/UserService.cs b/MainProject/Services/UserService.cs new file mode 100644 index 0000000..4061dbc --- /dev/null +++ b/MainProject/Services/UserService.cs @@ -0,0 +1,59 @@ + +using System.Collections; +using BasicDotnetTemplate.MainProject.Core.Database; +using BasicDotnetTemplate.MainProject.Models.Database.SqlServer; +using Microsoft.EntityFrameworkCore; + +namespace BasicDotnetTemplate.MainProject.Services; + +public interface IUserService +{ + Task GetUserByUsernameAndPassword(string username, string password); +} + +public class UserService : BaseService, IUserService +{ + + public UserService( + IConfiguration configuration, + SqlServerContext sqlServerContext + ) : base(configuration, sqlServerContext) + { } + + private IQueryable GetUsers() + { + return this._sqlServerContext.Users.Where(x => !x.IsDeleted); + } + + private IQueryable GetUserByUsername(string username) + { + return this._sqlServerContext.Users + .Where(x => + !x.IsDeleted && + String.Equals(x.Username, username, StringComparison.CurrentCultureIgnoreCase) + ); + } + + + public async Task GetUserByUsernameAndPassword(string username, string password) + { + User? user = null; + + try + { + user = await this.GetUserByUsername(username).FirstOrDefaultAsync(); + if (user != null) + { + var encryptedPassword = user.PasswordHash; + Console.WriteLine(encryptedPassword); + } + } + catch (Exception exception) + { + Console.WriteLine(exception.Message); + } + + return user; + } +} + diff --git a/MainProject/Utils/ProgramUtils.cs b/MainProject/Utils/ProgramUtils.cs index faa2bcd..7bbd154 100644 --- a/MainProject/Utils/ProgramUtils.cs +++ b/MainProject/Utils/ProgramUtils.cs @@ -214,6 +214,7 @@ public static class ProgramUtils Logger.Info("[ProgramUtils][AddScopes] Adding scopes"); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); Logger.Info("[ProgramUtils][AddScopes] Done scopes"); } diff --git a/MainProject/appsettings.json b/MainProject/appsettings.json index 3fcf5e6..acc5e8b 100644 --- a/MainProject/appsettings.json +++ b/MainProject/appsettings.json @@ -28,7 +28,7 @@ "Microsoft.AspNetCore": "Warning" } }, - "JWTSettings": { + "JwtSettings": { "ValidAudience": "http://localhost:4200", "ValidIssuer": "http://localhost:5000", "Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr",