Adding authentication and authorization flow
This commit is contained in:
@@ -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<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace BasicDotnetTemplate.MainProject.Models.Settings;
|
||||
|
||||
public class JWTSettings
|
||||
public class JwtSettings
|
||||
{
|
||||
#nullable enable
|
||||
public string? ValidAudience { get; set; }
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
59
MainProject/Services/UserService.cs
Normal file
59
MainProject/Services/UserService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"JWTSettings": {
|
||||
"JwtSettings": {
|
||||
"ValidAudience": "http://localhost:4200",
|
||||
"ValidIssuer": "http://localhost:5000",
|
||||
"Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr",
|
||||
|
||||
Reference in New Issue
Block a user