Adding authentication and authorization flow

This commit is contained in:
2025-02-27 00:12:41 +01:00
parent 7926cf2f65
commit fbf5ef8c16
13 changed files with 187 additions and 24 deletions

View File

@@ -28,7 +28,7 @@
"Microsoft.AspNetCore": "Warning"
}
},
"JWTSettings": {
"JwtSettings": {
"ValidAudience": "http://localhost:4200",
"ValidIssuer": "http://localhost:5000",
"Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr",

View File

@@ -28,7 +28,7 @@
"Microsoft.AspNetCore": "Warning"
}
},
"JWTSettings": {
"JwtSettings": {
"ValidAudience": "http://localhost:4200",
"ValidIssuer": "http://localhost:5000",
"Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr",

View File

@@ -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
{
@@ -23,10 +24,67 @@ namespace BasicDotnetTemplate.MainProject.Core.Attributes
}
public void OnAuthorization(AuthorizationFilterContext context)
{
// If [AllowAnonymous], skip
if (context.ActionDescriptor.EndpointMetadata.Any(em => em is AllowAnonymousAttribute))
{
return;
}
var configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
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();
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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; }

View File

@@ -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

View File

@@ -1,6 +1,6 @@
namespace BasicDotnetTemplate.MainProject.Models.Settings;
public class JWTSettings
public class JwtSettings
{
#nullable enable
public string? ValidAudience { get; set; }

View File

@@ -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<AuthenticatedUser?> 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;

View File

@@ -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;
}
}

View File

@@ -4,6 +4,7 @@ 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;
@@ -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<Claim>
{
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);
}
}

View File

@@ -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<User?> GetUserByUsernameAndPassword(string username, string password);
}
public class UserService : BaseService, IUserService
{
public UserService(
IConfiguration configuration,
SqlServerContext sqlServerContext
) : base(configuration, sqlServerContext)
{ }
private IQueryable<User> GetUsers()
{
return this._sqlServerContext.Users.Where(x => !x.IsDeleted);
}
private IQueryable<User> GetUserByUsername(string username)
{
return this._sqlServerContext.Users
.Where(x =>
!x.IsDeleted &&
String.Equals(x.Username, username, StringComparison.CurrentCultureIgnoreCase)
);
}
public async Task<User?> 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;
}
}

View File

@@ -214,6 +214,7 @@ public static class ProgramUtils
Logger.Info("[ProgramUtils][AddScopes] Adding scopes");
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddScoped<IJwtService, JwtService>();
builder.Services.AddScoped<IUserService, UserService>();
Logger.Info("[ProgramUtils][AddScopes] Done scopes");
}

View File

@@ -28,7 +28,7 @@
"Microsoft.AspNetCore": "Warning"
}
},
"JWTSettings": {
"JwtSettings": {
"ValidAudience": "http://localhost:4200",
"ValidIssuer": "http://localhost:5000",
"Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr",