Add cleint-side and server-side services
This commit is contained in:
39
BlazorPolicyAuth/App.Services/AuthService/AuthService.cs
Normal file
39
BlazorPolicyAuth/App.Services/AuthService/AuthService.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using BlazorPolicyAuth.Models.ViewModels;
|
||||||
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.App.Services.AuthService;
|
||||||
|
|
||||||
|
public class AuthService(HttpClient httpClient, AuthenticationStateProvider authenticationStateProvider)
|
||||||
|
: IAuthService
|
||||||
|
{
|
||||||
|
public async Task<ServiceResponse<int>> Register(UserRegister request)
|
||||||
|
{
|
||||||
|
var result = await httpClient.PostAsJsonAsync("api/auth/register", request);
|
||||||
|
return await ReadResponse<int>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServiceResponse<string>> Login(UserLogin request)
|
||||||
|
{
|
||||||
|
var result = await httpClient.PostAsJsonAsync("api/auth/login", request);
|
||||||
|
return await ReadResponse<string>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServiceResponse<bool>> ChangePassword(UserChangePassword request)
|
||||||
|
{
|
||||||
|
var result = await httpClient.PostAsJsonAsync("api/auth/change-password", request.Password);
|
||||||
|
return await ReadResponse<bool>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsUserAuthenticated()
|
||||||
|
{
|
||||||
|
return (await authenticationStateProvider.GetAuthenticationStateAsync()).User.Identity.IsAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ServiceResponse<T>> ReadResponse<T>(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
return await response.Content.ReadFromJsonAsync<ServiceResponse<T>>();
|
||||||
|
|
||||||
|
return new ServiceResponse<T> { Success = false, Message = await response.Content.ReadAsStringAsync() };
|
||||||
|
}
|
||||||
|
}
|
||||||
11
BlazorPolicyAuth/App.Services/AuthService/IAuthService.cs
Normal file
11
BlazorPolicyAuth/App.Services/AuthService/IAuthService.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using BlazorPolicyAuth.Models.ViewModels;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.App.Services.AuthService;
|
||||||
|
|
||||||
|
public interface IAuthService
|
||||||
|
{
|
||||||
|
Task<ServiceResponse<int>> Register(UserRegister request);
|
||||||
|
Task<ServiceResponse<string>> Login(UserLogin request);
|
||||||
|
Task<ServiceResponse<bool>> ChangePassword(UserChangePassword request);
|
||||||
|
Task<bool> IsUserAuthenticated();
|
||||||
|
}
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
@page "/login"
|
@page "/login"
|
||||||
@using System.Security.Claims
|
@using BlazorPolicyAuth.App.Services.AuthService
|
||||||
@using BlazorPolicyAuth.Data
|
|
||||||
@using BlazorPolicyAuth.Models.ViewModels
|
@using BlazorPolicyAuth.Models.ViewModels
|
||||||
@using Microsoft.AspNetCore.Authentication
|
|
||||||
@using Microsoft.AspNetCore.Authentication.Cookies
|
|
||||||
@using Microsoft.EntityFrameworkCore
|
|
||||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IAuthService AuthService
|
@inject IAuthService AuthService
|
||||||
@@ -52,7 +48,7 @@
|
|||||||
{
|
{
|
||||||
Console.WriteLine("***");
|
Console.WriteLine("***");
|
||||||
Console.WriteLine(userLogin.Email);
|
Console.WriteLine(userLogin.Email);
|
||||||
var result = await AuthService.Login(userLogin.Email, userLogin.Password);
|
var result = await AuthService.Login(userLogin);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
_errorMessage = string.Empty;
|
_errorMessage = string.Empty;
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
@page "/register"
|
@page "/register"
|
||||||
|
@using BlazorPolicyAuth.App.Services.AuthService
|
||||||
@using BlazorPolicyAuth.Models.ViewModels
|
@using BlazorPolicyAuth.Models.ViewModels
|
||||||
|
|
||||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
|
||||||
@inject NavigationManager NavigationManager
|
|
||||||
@inject IAuthService AuthService
|
@inject IAuthService AuthService
|
||||||
|
|
||||||
<title>Register</title>
|
<title>Register</title>
|
||||||
|
|
||||||
<EditForm Model="user" OnValidSubmit="HandleRegistration" FormName="registerForm">
|
<EditForm Model="user" OnValidSubmit="HandleRegistration" OnInvalidSubmit="HandleInvalidSubmit" FormName="registerForm">
|
||||||
<DataAnnotationsValidator/>
|
<DataAnnotationsValidator/>
|
||||||
<div class="d-flex justify-content-center align-items-center">
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
<div class="col-md-4 p-5 shadow-sm border rounded-3">
|
<div class="col-md-4 p-5 shadow-sm border rounded-3">
|
||||||
@@ -46,16 +45,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
UserRegister user = new();
|
[SupplyParameterFromForm] private UserRegister user { get; set; }
|
||||||
|
|
||||||
private string message = string.Empty;
|
private string message = string.Empty;
|
||||||
private string messageCssClass = string.Empty;
|
private string messageCssClass = string.Empty;
|
||||||
private string errorMessage = string.Empty;
|
private string errorMessage = string.Empty;
|
||||||
|
|
||||||
async void HandleRegistration()
|
protected override void OnInitialized() => user ??= new();
|
||||||
|
|
||||||
|
async Task HandleRegistration()
|
||||||
{
|
{
|
||||||
var result = await AuthService.Register(user.Email, user.Password);
|
var result = await AuthService.Register(user);
|
||||||
message = result.Message;
|
message = result.Message;
|
||||||
messageCssClass = result.Success ? "text-success" : "text-danger";
|
messageCssClass = result.Success ? "text-success" : "text-danger";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async Task HandleInvalidSubmit()
|
||||||
|
{
|
||||||
|
message = "Data annotations validation failed.";
|
||||||
|
messageCssClass = "text-danger";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,5 +9,4 @@
|
|||||||
@using BlazorPolicyAuth
|
@using BlazorPolicyAuth
|
||||||
@using BlazorPolicyAuth.Components
|
@using BlazorPolicyAuth.Components
|
||||||
@using BlazorPolicyAuth.Components.Layout
|
@using BlazorPolicyAuth.Components.Layout
|
||||||
@using Microsoft.AspNetCore.Components.Authorization
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
@using BlazorPolicyAuth.Services.AuthService
|
|
||||||
55
BlazorPolicyAuth/HttpClientSetupService.cs
Normal file
55
BlazorPolicyAuth/HttpClientSetupService.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth;
|
||||||
|
/// <summary>
|
||||||
|
/// Source: https://www.duracellko.net/posts/2020/06/hosting-both-blazor-server-and-webassembly
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpClient"></param>
|
||||||
|
/// <param name="server"></param>
|
||||||
|
/// <param name="applicationLifetime"></param>
|
||||||
|
public class HttpClientSetupService(
|
||||||
|
HttpClient httpClient,
|
||||||
|
IServer server,
|
||||||
|
IHostApplicationLifetime applicationLifetime)
|
||||||
|
: BackgroundService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||||
|
private readonly IServer _server = server ?? throw new ArgumentNullException(nameof(server));
|
||||||
|
private readonly IHostApplicationLifetime _applicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
|
||||||
|
|
||||||
|
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
var applicationStartedToken = _applicationLifetime.ApplicationStarted;
|
||||||
|
if (applicationStartedToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
ConfigureHttpClient();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicationStartedToken.Register(ConfigureHttpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureHttpClient()
|
||||||
|
{
|
||||||
|
var serverAddresses = _server.Features.Get<IServerAddressesFeature>();
|
||||||
|
var address = serverAddresses.Addresses.FirstOrDefault();
|
||||||
|
if (address == null)
|
||||||
|
{
|
||||||
|
// Default ASP.NET Core Kestrel endpoint
|
||||||
|
address = "http://localhost:5000";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
address = address.Replace("*", "localhost", StringComparison.Ordinal);
|
||||||
|
address = address.Replace("+", "localhost", StringComparison.Ordinal);
|
||||||
|
address = address.Replace("[::]", "localhost", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseUri = new Uri(address);
|
||||||
|
_httpClient.BaseAddress = baseUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
using BlazorPolicyAuth;
|
using BlazorPolicyAuth;
|
||||||
using BlazorPolicyAuth.Components;
|
using BlazorPolicyAuth.Components;
|
||||||
using BlazorPolicyAuth.Data;
|
using BlazorPolicyAuth.Data;
|
||||||
|
using BlazorPolicyAuth.Models.ViewModels;
|
||||||
using BlazorPolicyAuth.Services.AuthService;
|
using BlazorPolicyAuth.Services.AuthService;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using ClientServices = BlazorPolicyAuth.App.Services;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -31,10 +33,18 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
|
|||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddCascadingAuthenticationState();
|
builder.Services.AddCascadingAuthenticationState();
|
||||||
|
|
||||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
|
// Blazor client services
|
||||||
|
builder.Services.AddScoped<ClientServices.AuthService.IAuthService, ClientServices.AuthService.AuthService>();
|
||||||
|
|
||||||
|
// Blazor server services
|
||||||
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||||
|
|
||||||
|
// Get server base address when application starts to properly configure HttpClient for client service to call server service
|
||||||
|
builder.Services.AddSingleton<HttpClient>();
|
||||||
|
builder.Services.AddSingleton<IHostedService, HttpClientSetupService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
@@ -53,4 +63,8 @@ app.MapStaticAssets();
|
|||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode();
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
|
// Blazor server routing
|
||||||
|
app.MapPost("/api/auth/register", async (UserRegister request, IAuthService authService) =>
|
||||||
|
await authService.Register(request));
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -12,43 +12,33 @@ using System.Text;
|
|||||||
|
|
||||||
namespace BlazorPolicyAuth.Services.AuthService;
|
namespace BlazorPolicyAuth.Services.AuthService;
|
||||||
|
|
||||||
public class AuthService : IAuthService
|
public class AuthService(AppDbContext context, IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
|
||||||
|
: IAuthService
|
||||||
{
|
{
|
||||||
private readonly AppDbContext _context;
|
public async Task<ServiceResponse<int>> Register (UserRegister request)
|
||||||
private readonly IConfiguration _configuration;
|
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
||||||
|
|
||||||
public AuthService(AppDbContext context, IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
|
|
||||||
{
|
{
|
||||||
_context = context;
|
if (await UserExists(request.Email))
|
||||||
_configuration = configuration;
|
|
||||||
_httpContextAccessor = httpContextAccessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ServiceResponse<int>> Register ( string email, string password)
|
|
||||||
{
|
|
||||||
if (await UserExists(email))
|
|
||||||
{
|
{
|
||||||
return new ServiceResponse<int> { Success = false, Message = "User already exist." };
|
return new ServiceResponse<int> { Success = false, Message = "User already exist." };
|
||||||
}
|
}
|
||||||
|
|
||||||
CreatePasswordHash(password, out byte[] passwordHash, out byte[] passwordSalt);
|
CreatePasswordHash(request.Password, out byte[] passwordHash, out byte[] passwordSalt);
|
||||||
var user = new UserAccount
|
var user = new UserAccount
|
||||||
{
|
{
|
||||||
UserName = email,
|
UserName = request.Email,
|
||||||
PasswordHash = passwordHash,
|
PasswordHash = passwordHash,
|
||||||
PasswordSalt = passwordSalt
|
PasswordSalt = passwordSalt
|
||||||
};
|
};
|
||||||
|
|
||||||
_context.UserAccounts.Add(user);
|
context.UserAccounts.Add(user);
|
||||||
await _context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
return new ServiceResponse<int> { Data = user.Id, Message = "Registration successful" };
|
return new ServiceResponse<int> { Data = user.Id, Message = "Registration successful" };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UserExists(string email)
|
public async Task<bool> UserExists(string email)
|
||||||
{
|
{
|
||||||
if (await _context.UserAccounts.AnyAsync(user => user.UserName.ToLower().Equals(email.ToLower())))
|
if (await context.UserAccounts.AnyAsync(user => user.UserName.ToLower().Equals(email.ToLower())))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -58,7 +48,7 @@ public class AuthService : IAuthService
|
|||||||
public async Task<ServiceResponse<string>> Login(string email, string password)
|
public async Task<ServiceResponse<string>> Login(string email, string password)
|
||||||
{
|
{
|
||||||
var response = new ServiceResponse<string>();
|
var response = new ServiceResponse<string>();
|
||||||
var user = await _context.UserAccounts.FirstOrDefaultAsync(u => u.UserName.ToLower().Equals(email.ToLower()));
|
var user = await context.UserAccounts.FirstOrDefaultAsync(u => u.UserName.ToLower().Equals(email.ToLower()));
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
response.Success = false;
|
response.Success = false;
|
||||||
@@ -79,7 +69,7 @@ public class AuthService : IAuthService
|
|||||||
|
|
||||||
public async Task<ServiceResponse<bool>> ChangePassword(int userId, string newPassword)
|
public async Task<ServiceResponse<bool>> ChangePassword(int userId, string newPassword)
|
||||||
{
|
{
|
||||||
var user = await _context.UserAccounts.FindAsync(userId);
|
var user = await context.UserAccounts.FindAsync(userId);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
return new ServiceResponse<bool> { Success = false, Message = "User not found." };
|
return new ServiceResponse<bool> { Success = false, Message = "User not found." };
|
||||||
@@ -90,18 +80,18 @@ public class AuthService : IAuthService
|
|||||||
user.PasswordHash = passwordHash;
|
user.PasswordHash = passwordHash;
|
||||||
user.PasswordSalt = passwordSalt;
|
user.PasswordSalt = passwordSalt;
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
return new ServiceResponse<bool> { Data = true, Message = "Password has been changed." };
|
return new ServiceResponse<bool> { Data = true, Message = "Password has been changed." };
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetUserId() => int.Parse(_httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
|
public int GetUserId() => int.Parse(httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||||
|
|
||||||
public string GetUserEmail() => _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.Name);
|
public string GetUserEmail() => httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.Name);
|
||||||
|
|
||||||
public async Task<UserAccount> GetUserByEmail(string email)
|
public async Task<UserAccount> GetUserByEmail(string email)
|
||||||
{
|
{
|
||||||
return await _context.UserAccounts.FirstOrDefaultAsync(u => u.UserName.Equals(email));
|
return await context.UserAccounts.FirstOrDefaultAsync(u => u.UserName.Equals(email));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
|
private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
|
||||||
@@ -127,7 +117,7 @@ public class AuthService : IAuthService
|
|||||||
new(ClaimTypes.Role, user.Role)
|
new(ClaimTypes.Role, user.Role)
|
||||||
};
|
};
|
||||||
|
|
||||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("AppSettings:Token").Value));
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("AppSettings:Token").Value));
|
||||||
|
|
||||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace BlazorPolicyAuth.Services.AuthService;
|
|||||||
|
|
||||||
public interface IAuthService
|
public interface IAuthService
|
||||||
{
|
{
|
||||||
Task<ServiceResponse<int>> Register(string email, string password);
|
Task<ServiceResponse<int>> Register(UserRegister request);
|
||||||
Task<bool> UserExists(string email);
|
Task<bool> UserExists(string email);
|
||||||
Task<ServiceResponse<string>> Login(string email, string password);
|
Task<ServiceResponse<string>> Login(string email, string password);
|
||||||
Task<ServiceResponse<bool>> ChangePassword(int userId, string newPassword);
|
Task<ServiceResponse<bool>> ChangePassword(int userId, string newPassword);
|
||||||
|
|||||||
Reference in New Issue
Block a user