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"
|
||||
@using System.Security.Claims
|
||||
@using BlazorPolicyAuth.Data
|
||||
@using BlazorPolicyAuth.App.Services.AuthService
|
||||
@using BlazorPolicyAuth.Models.ViewModels
|
||||
@using Microsoft.AspNetCore.Authentication
|
||||
@using Microsoft.AspNetCore.Authentication.Cookies
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IAuthService AuthService
|
||||
@@ -52,7 +48,7 @@
|
||||
{
|
||||
Console.WriteLine("***");
|
||||
Console.WriteLine(userLogin.Email);
|
||||
var result = await AuthService.Login(userLogin.Email, userLogin.Password);
|
||||
var result = await AuthService.Login(userLogin);
|
||||
if (result.Success)
|
||||
{
|
||||
_errorMessage = string.Empty;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
@page "/register"
|
||||
@using BlazorPolicyAuth.App.Services.AuthService
|
||||
@using BlazorPolicyAuth.Models.ViewModels
|
||||
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IAuthService AuthService
|
||||
|
||||
<title>Register</title>
|
||||
|
||||
<EditForm Model="user" OnValidSubmit="HandleRegistration" FormName="registerForm">
|
||||
<EditForm Model="user" OnValidSubmit="HandleRegistration" OnInvalidSubmit="HandleInvalidSubmit" FormName="registerForm">
|
||||
<DataAnnotationsValidator/>
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<div class="col-md-4 p-5 shadow-sm border rounded-3">
|
||||
@@ -46,16 +45,24 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
UserRegister user = new();
|
||||
[SupplyParameterFromForm] private UserRegister user { get; set; }
|
||||
|
||||
private string message = string.Empty;
|
||||
private string messageCssClass = 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;
|
||||
messageCssClass = result.Success ? "text-success" : "text-danger";
|
||||
}
|
||||
|
||||
async Task HandleInvalidSubmit()
|
||||
{
|
||||
message = "Data annotations validation failed.";
|
||||
messageCssClass = "text-danger";
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,3 @@
|
||||
@using BlazorPolicyAuth.Components
|
||||
@using BlazorPolicyAuth.Components.Layout
|
||||
@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.Components;
|
||||
using BlazorPolicyAuth.Data;
|
||||
using BlazorPolicyAuth.Models.ViewModels;
|
||||
using BlazorPolicyAuth.Services.AuthService;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ClientServices = BlazorPolicyAuth.App.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -31,10 +33,18 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
|
||||
});
|
||||
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
|
||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||
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();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
@@ -53,4 +63,8 @@ app.MapStaticAssets();
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
// Blazor server routing
|
||||
app.MapPost("/api/auth/register", async (UserRegister request, IAuthService authService) =>
|
||||
await authService.Register(request));
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -12,43 +12,33 @@ using System.Text;
|
||||
|
||||
namespace BlazorPolicyAuth.Services.AuthService;
|
||||
|
||||
public class AuthService : IAuthService
|
||||
public class AuthService(AppDbContext context, IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
|
||||
: IAuthService
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public AuthService(AppDbContext context, IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
|
||||
public async Task<ServiceResponse<int>> Register (UserRegister request)
|
||||
{
|
||||
_context = context;
|
||||
_configuration = configuration;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public async Task<ServiceResponse<int>> Register ( string email, string password)
|
||||
{
|
||||
if (await UserExists(email))
|
||||
if (await UserExists(request.Email))
|
||||
{
|
||||
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
|
||||
{
|
||||
UserName = email,
|
||||
UserName = request.Email,
|
||||
PasswordHash = passwordHash,
|
||||
PasswordSalt = passwordSalt
|
||||
};
|
||||
|
||||
_context.UserAccounts.Add(user);
|
||||
await _context.SaveChangesAsync();
|
||||
context.UserAccounts.Add(user);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return new ServiceResponse<int> { Data = user.Id, Message = "Registration successful" };
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -58,7 +48,7 @@ public class AuthService : IAuthService
|
||||
public async Task<ServiceResponse<string>> Login(string email, string password)
|
||||
{
|
||||
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)
|
||||
{
|
||||
response.Success = false;
|
||||
@@ -79,7 +69,7 @@ public class AuthService : IAuthService
|
||||
|
||||
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)
|
||||
{
|
||||
return new ServiceResponse<bool> { Success = false, Message = "User not found." };
|
||||
@@ -90,18 +80,18 @@ public class AuthService : IAuthService
|
||||
user.PasswordHash = passwordHash;
|
||||
user.PasswordSalt = passwordSalt;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
@@ -127,7 +117,7 @@ public class AuthService : IAuthService
|
||||
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);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace BlazorPolicyAuth.Services.AuthService;
|
||||
|
||||
public interface IAuthService
|
||||
{
|
||||
Task<ServiceResponse<int>> Register(string email, string password);
|
||||
Task<ServiceResponse<int>> Register(UserRegister request);
|
||||
Task<bool> UserExists(string email);
|
||||
Task<ServiceResponse<string>> Login(string email, string password);
|
||||
Task<ServiceResponse<bool>> ChangePassword(int userId, string newPassword);
|
||||
|
||||
Reference in New Issue
Block a user