From 35c34991bf2d262de0ad4859dfa2d0ef3abbab34 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Sat, 3 Oct 2026 15:32:03 +0100 Subject: [PATCH 1/3] use the correct token in the API client --- .../OidcUserAccessTokenProviderTests.cs | 79 +++++++++++++++++++ .../OidcUserAccessTokenProvider.cs | 41 ++++++++++ .../Common/BoostrapperExtensions.cs | 5 +- .../Client/ContractMethods.cs | 16 ++-- .../Client/DateMethods.cs | 16 ++-- .../Client/EstateMethods.cs | 8 +- .../Client/FileImportMethods.cs | 4 +- .../Client/FileUploadMethods.cs | 4 +- .../Client/IUserAccessTokenProvider.cs | 8 ++ .../Client/MerchantMethods.cs | 48 +++++------ .../Client/OperatorMethods.cs | 10 +-- .../Client/SettlementMethods.cs | 2 +- .../Client/TransactionMethods.cs | 14 ++-- 13 files changed, 191 insertions(+), 64 deletions(-) create mode 100644 EstateManagementUI.BlazorServer.Tests/Authentication/OidcUserAccessTokenProviderTests.cs create mode 100644 EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs create mode 100644 EstateManagmentUI.BusinessLogic/Client/IUserAccessTokenProvider.cs diff --git a/EstateManagementUI.BlazorServer.Tests/Authentication/OidcUserAccessTokenProviderTests.cs b/EstateManagementUI.BlazorServer.Tests/Authentication/OidcUserAccessTokenProviderTests.cs new file mode 100644 index 00000000..900f58f2 --- /dev/null +++ b/EstateManagementUI.BlazorServer.Tests/Authentication/OidcUserAccessTokenProviderTests.cs @@ -0,0 +1,79 @@ +using EstateManagementUI.BlazorServer.Authentication; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; + +namespace EstateManagementUI.BlazorServer.Tests.Authentication; + +public sealed class OidcUserAccessTokenProviderTests +{ + [Fact] + public async Task GetAccessTokenAsync_ReturnsAccessTokenFromAuthenticatedUserSession() + { + var httpContext = new DefaultHttpContext(); + var authenticationProperties = new AuthenticationProperties(); + authenticationProperties.StoreTokens(new[] + { + new AuthenticationToken { Name = "access_token", Value = "user-access-token" } + }); + var authenticationService = new StubAuthenticationService( + AuthenticateResult.Success(new AuthenticationTicket( + new System.Security.Claims.ClaimsPrincipal(new System.Security.Claims.ClaimsIdentity("Cookies")), + authenticationProperties, + CookieAuthenticationDefaults.AuthenticationScheme))); + httpContext.RequestServices = new ServiceCollection() + .AddSingleton(authenticationService) + .BuildServiceProvider(); + var provider = new OidcUserAccessTokenProvider( + new HttpContextAccessor { HttpContext = httpContext }); + + var result = await provider.GetAccessTokenAsync(CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + result.Data.ShouldBe("user-access-token"); + } + + [Fact] + public async Task GetAccessTokenAsync_WithoutAuthenticatedSession_ReturnsFailure() + { + var httpContext = new DefaultHttpContext + { + RequestServices = new ServiceCollection() + .AddSingleton(new StubAuthenticationService(AuthenticateResult.NoResult())) + .BuildServiceProvider() + }; + var provider = new OidcUserAccessTokenProvider( + new HttpContextAccessor { HttpContext = httpContext }); + + var result = await provider.GetAccessTokenAsync(CancellationToken.None); + + result.IsFailed.ShouldBeTrue(); + } + + private sealed class StubAuthenticationService : IAuthenticationService + { + public AuthenticateResult Result { get; } + + public StubAuthenticationService(AuthenticateResult result) + { + this.Result = result; + } + + public Task AuthenticateAsync(HttpContext context, string? scheme) + => Task.FromResult(this.Result); + + public Task ChallengeAsync(HttpContext context, string? scheme, AuthenticationProperties? properties) + => Task.CompletedTask; + + public Task ForbidAsync(HttpContext context, string? scheme, AuthenticationProperties? properties) + => Task.CompletedTask; + + public Task SignInAsync(HttpContext context, string? scheme, System.Security.Claims.ClaimsPrincipal principal, AuthenticationProperties? properties) + => Task.CompletedTask; + + public Task SignOutAsync(HttpContext context, string? scheme, AuthenticationProperties? properties) + => Task.CompletedTask; + } +} diff --git a/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs b/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs new file mode 100644 index 00000000..65e03454 --- /dev/null +++ b/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs @@ -0,0 +1,41 @@ +using EstateManagementUI.BusinessLogic.Client; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using SimpleResults; + +namespace EstateManagementUI.BlazorServer.Authentication; + +public sealed class OidcUserAccessTokenProvider : IUserAccessTokenProvider +{ + private readonly IHttpContextAccessor HttpContextAccessor; + + public OidcUserAccessTokenProvider(IHttpContextAccessor httpContextAccessor) + { + this.HttpContextAccessor = httpContextAccessor; + } + + public async Task> GetAccessTokenAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var httpContext = this.HttpContextAccessor.HttpContext; + if (httpContext is null) + return Result.Failure("No active HTTP context is available."); + + var authenticationService = httpContext.RequestServices.GetRequiredService(); + var authenticationResult = await authenticationService.AuthenticateAsync( + httpContext, + CookieAuthenticationDefaults.AuthenticationScheme); + + if (!authenticationResult.Succeeded || authenticationResult.Properties is null) + return Result.Failure("The current user is not authenticated."); + + var accessToken = authenticationResult.Properties.GetTokens() + .SingleOrDefault(token => token.Name == "access_token")?.Value; + if (String.IsNullOrWhiteSpace(accessToken)) + return Result.Failure("The current user session does not contain an access token."); + + return Result.Success(accessToken); + } +} diff --git a/EstateManagementUI.BlazorServer/Common/BoostrapperExtensions.cs b/EstateManagementUI.BlazorServer/Common/BoostrapperExtensions.cs index 54e8c628..76bd24f3 100644 --- a/EstateManagementUI.BlazorServer/Common/BoostrapperExtensions.cs +++ b/EstateManagementUI.BlazorServer/Common/BoostrapperExtensions.cs @@ -1,4 +1,5 @@ using ClientProxyBase; +using EstateManagementUI.BlazorServer.Authentication; using EstateManagementUI.BlazorServer.Permissions; using EstateManagementUI.BlazorServer.Testing; using EstateManagementUI.BlazorServer.TokenManagement; @@ -175,7 +176,7 @@ public static WebApplicationBuilder ConfigureLiveAuthentication(this WebApplicat options.Authority = authorityAddress; options.ClientId = builder.Configuration["Authentication:ClientId"]; options.ClientSecret = builder.Configuration["Authentication:ClientSecret"]; - options.ResponseType = "code id_token"; + options.ResponseType = "code"; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; @@ -191,6 +192,7 @@ public static WebApplicationBuilder ConfigureLiveAuthentication(this WebApplicat // Add additional scopes from old app options.Scope.Add("fileProcessor"); options.Scope.Add("transactionProcessor"); + options.Scope.Add("estateReporting"); options.RequireHttpsMetadata = false; // For development - set to true in production @@ -263,6 +265,7 @@ public static WebApplicationBuilder RegisterUIServices(this WebApplicationBuilde return builder; } public static WebApplicationBuilder RegisterClients(this WebApplicationBuilder builder) { + builder.Services.AddSingleton(); builder.Services.RegisterHttpClient(); builder.Services.RegisterHttpClient(); builder.Services.RegisterHttpClient(); diff --git a/EstateManagmentUI.BusinessLogic/Client/ContractMethods.cs b/EstateManagmentUI.BusinessLogic/Client/ContractMethods.cs index f454cc06..d0223010 100644 --- a/EstateManagmentUI.BusinessLogic/Client/ContractMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/ContractMethods.cs @@ -35,7 +35,7 @@ Task RemoveTransactionFeeFromProduct(ContractCommands.RemoveTransactionF public partial class ApiClient : IApiClient { public async Task>> GetRecentContracts(ContractQueries.GetRecentContractsQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -52,7 +52,7 @@ public partial class ApiClient : IApiClient { public async Task>> GetContracts(ContractQueries.GetContractsForDropDownQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -69,7 +69,7 @@ public partial class ApiClient : IApiClient { public async Task>> GetContracts(ContractQueries.GetContractsQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -86,7 +86,7 @@ public partial class ApiClient : IApiClient { public async Task> GetContract(ContractQueries.GetContractQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -102,7 +102,7 @@ public partial class ApiClient : IApiClient { public async Task CreateContract(ContractCommands.CreateContractCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -117,7 +117,7 @@ public async Task CreateContract(ContractCommands.CreateContractCommand public async Task AddProductToContract(ContractCommands.AddProductToContractCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -132,7 +132,7 @@ public async Task AddProductToContract(ContractCommands.AddProductToCont public async Task AddTransactionFeeToProduct(ContractCommands.AddTransactionFeeToProductCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -149,7 +149,7 @@ public async Task AddTransactionFeeToProduct(ContractCommands.AddTransac public async Task RemoveTransactionFeeFromProduct(ContractCommands.RemoveTransactionFeeFromProductCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); diff --git a/EstateManagmentUI.BusinessLogic/Client/DateMethods.cs b/EstateManagmentUI.BusinessLogic/Client/DateMethods.cs index 43b67156..ec0e1933 100644 --- a/EstateManagmentUI.BusinessLogic/Client/DateMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/DateMethods.cs @@ -24,23 +24,25 @@ Task>> GetComparisonDates(DateQueries.GetCompar public partial class ApiClient : IApiClient { private readonly IEstateReportingApiClient EstateReportingApiClient; private readonly ISecurityServiceClient SecurityServiceClient; + private readonly IUserAccessTokenProvider UserAccessTokenProvider; private readonly ITransactionProcessorClient TransactionProcessorClient; private readonly IFileProcessorClient FileProcessorClient; public ApiClient(IEstateReportingApiClient estateReportingApiClient, ISecurityServiceClient securityServiceClient, + IUserAccessTokenProvider userAccessTokenProvider, ITransactionProcessorClient transactionProcessorClient, IFileProcessorClient fileProcessorClient) { this.EstateReportingApiClient = estateReportingApiClient; this.SecurityServiceClient = securityServiceClient; + this.UserAccessTokenProvider = userAccessTokenProvider; this.TransactionProcessorClient = transactionProcessorClient; this.FileProcessorClient = fileProcessorClient; } public async Task>> GetComparisonDates(DateQueries.GetComparisonDatesQuery request, CancellationToken cancellationToken) { - // Get a token here - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); Result> apiResult = await this.EstateReportingApiClient.GetComparisonDates(token.Data, request.EstateId, cancellationToken); @@ -53,14 +55,8 @@ public async Task>> GetComparisonDates(DateQuer return Result.Success(comparisonDates); } - private async Task> GetToken(CancellationToken cancellationToken) { - // Get a token here. - var clientId = ConfigurationReader.GetValueOrDefault("AppSettings", "ClientId", ""); - var clientSecret = ConfigurationReader.GetValueOrDefault("AppSettings", "ClientSecret", ""); - Result? token = await this.SecurityServiceClient.GetToken(clientId, clientSecret, cancellationToken); - if (token.IsFailed) - return ResultHelpers.CreateFailure(token); - return Result.Success(token.Data.AccessToken); + private async Task> GetUserToken(CancellationToken cancellationToken) { + return await this.UserAccessTokenProvider.GetAccessTokenAsync(cancellationToken); } } } diff --git a/EstateManagmentUI.BusinessLogic/Client/EstateMethods.cs b/EstateManagmentUI.BusinessLogic/Client/EstateMethods.cs index e2007bb8..2d1db170 100644 --- a/EstateManagmentUI.BusinessLogic/Client/EstateMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/EstateMethods.cs @@ -29,7 +29,7 @@ public partial class ApiClient : IApiClient { public async Task> GetEstate(EstateQueries.GetEstateQuery request, CancellationToken cancellationToken) { // Get a token here - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -45,7 +45,7 @@ public partial class ApiClient : IApiClient { public async Task>> GetEstateAssignedOperators(EstateQueries.GetAssignedOperatorsQuery request, CancellationToken cancellationToken) { // Get a token here - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -61,7 +61,7 @@ public partial class ApiClient : IApiClient { public async Task RemoveEstateOperator(EstateCommands.RemoveOperatorFromEstateCommand request, CancellationToken cancellationToken) { // Get a token here - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -76,7 +76,7 @@ public async Task AddEstateOperator(EstateCommands.AddOperatorToEstateCo CancellationToken cancellationToken) { // Get a token here - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); diff --git a/EstateManagmentUI.BusinessLogic/Client/FileImportMethods.cs b/EstateManagmentUI.BusinessLogic/Client/FileImportMethods.cs index 57e53fe7..bb1facd2 100644 --- a/EstateManagmentUI.BusinessLogic/Client/FileImportMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/FileImportMethods.cs @@ -14,7 +14,7 @@ public partial interface IApiClient { public partial class ApiClient : IApiClient { public async Task>> GetFileImportLogsList(FileProcessingQueries.GetFileImportLogsListQuery query, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -31,7 +31,7 @@ public partial class ApiClient : IApiClient { public async Task> GetFileImportLog(FileProcessingQueries.GetFileImportLogQuery query, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); diff --git a/EstateManagmentUI.BusinessLogic/Client/FileUploadMethods.cs b/EstateManagmentUI.BusinessLogic/Client/FileUploadMethods.cs index d6a554f4..90982c69 100644 --- a/EstateManagmentUI.BusinessLogic/Client/FileUploadMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/FileUploadMethods.cs @@ -24,7 +24,7 @@ public partial class ApiClient { public async Task>> GetFileProfiles(CancellationToken cancellationToken = default) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) { return ResultHelpers.CreateFailure(token); @@ -57,7 +57,7 @@ public async Task> UploadFileAsync(Guid estateId, return Result.Failure("A file name is required."); } - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) { return ResultHelpers.CreateFailure(token); diff --git a/EstateManagmentUI.BusinessLogic/Client/IUserAccessTokenProvider.cs b/EstateManagmentUI.BusinessLogic/Client/IUserAccessTokenProvider.cs new file mode 100644 index 00000000..0a307e00 --- /dev/null +++ b/EstateManagmentUI.BusinessLogic/Client/IUserAccessTokenProvider.cs @@ -0,0 +1,8 @@ +using SimpleResults; + +namespace EstateManagementUI.BusinessLogic.Client; + +public interface IUserAccessTokenProvider +{ + Task> GetAccessTokenAsync(CancellationToken cancellationToken); +} diff --git a/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs b/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs index f67b8947..5f8d29e9 100644 --- a/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs @@ -44,7 +44,7 @@ public partial class ApiClient : IApiClient { public async Task AddOperatorToMerchant(MerchantCommands.AddOperatorToMerchantCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -59,7 +59,7 @@ public async Task AddOperatorToMerchant(MerchantCommands.AddOperatorToMe public async Task RemoveContractFromMerchant(MerchantCommands.RemoveContractFromMerchantCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -72,7 +72,7 @@ public async Task RemoveContractFromMerchant(MerchantCommands.RemoveCont public async Task AddContractToMerchant(MerchantCommands.AssignContractToMerchantCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -87,7 +87,7 @@ public async Task AddContractToMerchant(MerchantCommands.AssignContractT public async Task AddDeviceToMerchant(MerchantCommands.AddMerchantDeviceCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -102,7 +102,7 @@ public async Task AddDeviceToMerchant(MerchantCommands.AddMerchantDevice public async Task SwapMerchantDevice(MerchantCommands.SwapMerchantDeviceCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -117,7 +117,7 @@ public async Task SwapMerchantDevice(MerchantCommands.SwapMerchantDevice public async Task MakeMerchantDeposit(MerchantCommands.MakeMerchantDepositCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -132,7 +132,7 @@ public async Task MakeMerchantDeposit(MerchantCommands.MakeMerchantDepos public async Task CreateMerchant(MerchantCommands.CreateMerchantCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -160,7 +160,7 @@ public async Task CreateMerchant(MerchantCommands.CreateMerchantCommand public async Task> GetMerchantOpeningHours(MerchantQueries.GetMerchantOpeningHoursQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -176,7 +176,7 @@ public async Task CreateMerchant(MerchantCommands.CreateMerchantCommand public async Task UpdateMerchantSchedule(MerchantCommands.UpdateMerchantScheduleCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -200,7 +200,7 @@ public async Task UpdateMerchantSchedule(MerchantCommands.UpdateMerchant public async Task CreateMerchantSchedule(MerchantCommands.CreateMerchantScheduleCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -223,7 +223,7 @@ public async Task CreateMerchantSchedule(MerchantCommands.CreateMerchant public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOperatorFromMerchantCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -238,7 +238,7 @@ public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOper CancellationToken cancellationToken) { // Get a token here - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -254,7 +254,7 @@ public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOper public async Task>> GetRecentMerchants(MerchantQueries.GetRecentMerchantsQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -271,7 +271,7 @@ public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOper public async Task>> GetMerchants(MerchantQueries.GetMerchantsQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -289,7 +289,7 @@ public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOper public async Task> GetMerchant(MerchantQueries.GetMerchantQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -305,7 +305,7 @@ public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOper public async Task> GetMerchantSchedule(MerchantQueries.GetMerchantScheduleQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -321,7 +321,7 @@ public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOper public async Task>> GetMerchantOperators(MerchantQueries.GetMerchantOperatorsQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -337,7 +337,7 @@ public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOper public async Task>> GetMerchantContracts(MerchantQueries.GetMerchantContractsQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -353,7 +353,7 @@ public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOper public async Task>> GetMerchantDevices(MerchantQueries.GetMerchantDevicesQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -370,7 +370,7 @@ public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOper public async Task UpdateMerchant(MerchantCommands.UpdateMerchantCommand request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -389,7 +389,7 @@ public async Task UpdateMerchant(MerchantCommands.UpdateMerchantCommand public async Task UpdateMerchantOpeningHours(MerchantCommands.UpdateMerchantOpeningHoursCommand request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -414,7 +414,7 @@ public async Task UpdateMerchantOpeningHours(MerchantCommands.UpdateMerc public async Task UpdateMerchantAddress(MerchantCommands.UpdateMerchantCommand request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -436,7 +436,7 @@ public async Task UpdateMerchantAddress(MerchantCommands.UpdateMerchantC public async Task UpdateMerchantContact(MerchantCommands.UpdateMerchantCommand request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -458,7 +458,7 @@ public async Task UpdateMerchantContact(MerchantCommands.UpdateMerchantC public async Task>> GetMerchants(MerchantQueries.GetMerchantsForDropDownQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); diff --git a/EstateManagmentUI.BusinessLogic/Client/OperatorMethods.cs b/EstateManagmentUI.BusinessLogic/Client/OperatorMethods.cs index 4d7eaf62..2b98ff75 100644 --- a/EstateManagmentUI.BusinessLogic/Client/OperatorMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/OperatorMethods.cs @@ -31,7 +31,7 @@ public partial class ApiClient : IApiClient { public async Task>> GetOperators(OperatorQueries.GetOperatorsQuery request, CancellationToken cancellationToken) { // Get a token here - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -49,7 +49,7 @@ public partial class ApiClient : IApiClient { CancellationToken cancellationToken) { // Get a token here - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -67,7 +67,7 @@ public partial class ApiClient : IApiClient { CancellationToken cancellationToken) { // Get a token here - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -83,7 +83,7 @@ public partial class ApiClient : IApiClient { public async Task UpdateOperator(OperatorCommands.UpdateOperatorCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -99,7 +99,7 @@ public async Task UpdateOperator(OperatorCommands.UpdateOperatorCommand public async Task CreateOperator(OperatorCommands.CreateOperatorCommand request, CancellationToken cancellationToken) { - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); diff --git a/EstateManagmentUI.BusinessLogic/Client/SettlementMethods.cs b/EstateManagmentUI.BusinessLogic/Client/SettlementMethods.cs index b2925472..816494d2 100644 --- a/EstateManagmentUI.BusinessLogic/Client/SettlementMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/SettlementMethods.cs @@ -14,7 +14,7 @@ public partial class ApiClient { public async Task> GetTodaysSettlement(SettlementQueries.GetTodaysSettlementQuery request, CancellationToken cancellationToken) { // Get a token here - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); diff --git a/EstateManagmentUI.BusinessLogic/Client/TransactionMethods.cs b/EstateManagmentUI.BusinessLogic/Client/TransactionMethods.cs index e43e9a98..772ed646 100644 --- a/EstateManagmentUI.BusinessLogic/Client/TransactionMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/TransactionMethods.cs @@ -24,7 +24,7 @@ public partial class ApiClient : IApiClient { public async Task> GetTodaysSales(TransactionQueries.GetTodaysSalesQuery request, CancellationToken cancellationToken) { // Get a token here - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -42,7 +42,7 @@ public async Task> GetTodaysFailedSales(TransactionQuer CancellationToken cancellationToken) { // Get a token here - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -58,7 +58,7 @@ public async Task> GetTodaysFailedSales(TransactionQuer public async Task> GetTransactionDetailReport(TransactionQueries.GetTransactionDetailQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -82,7 +82,7 @@ public async Task> GetTodaysFailedSales(TransactionQuer public async Task> GetMerchantTransactionSummary(TransactionQueries.GetMerchantTransactionSummaryQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -113,7 +113,7 @@ public async Task> GetTodaysFailedSales(TransactionQuer public async Task> GetOperatorTransactionSummary(TransactionQueries.GetOperatorTransactionSummaryQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -145,7 +145,7 @@ public async Task> GetTodaysFailedSales(TransactionQuer public async Task> GetProductPerformance(TransactionQueries.GetProductPerformanceQuery request, CancellationToken cancellationToken) { - Result token = await this.GetToken(cancellationToken); + Result token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); @@ -162,7 +162,7 @@ public async Task> GetTodaysFailedSales(TransactionQuer public async Task>> GetTodaysSalesByHour(TransactionQueries.GetTodaysSalesByHourQuery request, CancellationToken cancellationToken) { // Get a token here - var token = await this.GetToken(cancellationToken); + var token = await this.GetUserToken(cancellationToken); if (token.IsFailed) return ResultHelpers.CreateFailure(token); From a33dd8492c61b2fc0e6115477433f8b50483c776 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Sat, 3 Oct 2026 15:47:23 +0100 Subject: [PATCH 2/3] Fix OIDC token access in Blazor circuits --- .../OidcUserAccessTokenProviderTests.cs | 56 +++++-------------- .../OidcUserAccessTokenProvider.cs | 29 ++++------ .../Common/BoostrapperExtensions.cs | 31 ++++++---- .../Common/TestAuthenticationHandler.cs | 2 + .../AutomaticTokenManagementCookieEvents.cs | 12 ++++ 5 files changed, 58 insertions(+), 72 deletions(-) diff --git a/EstateManagementUI.BlazorServer.Tests/Authentication/OidcUserAccessTokenProviderTests.cs b/EstateManagementUI.BlazorServer.Tests/Authentication/OidcUserAccessTokenProviderTests.cs index 900f58f2..bfb3e016 100644 --- a/EstateManagementUI.BlazorServer.Tests/Authentication/OidcUserAccessTokenProviderTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Authentication/OidcUserAccessTokenProviderTests.cs @@ -1,8 +1,6 @@ using EstateManagementUI.BlazorServer.Authentication; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Components.Authorization; +using System.Security.Claims; using Shouldly; namespace EstateManagementUI.BlazorServer.Tests.Authentication; @@ -12,22 +10,12 @@ public sealed class OidcUserAccessTokenProviderTests [Fact] public async Task GetAccessTokenAsync_ReturnsAccessTokenFromAuthenticatedUserSession() { - var httpContext = new DefaultHttpContext(); - var authenticationProperties = new AuthenticationProperties(); - authenticationProperties.StoreTokens(new[] + var principal = new ClaimsPrincipal(new ClaimsIdentity(new[] { - new AuthenticationToken { Name = "access_token", Value = "user-access-token" } - }); - var authenticationService = new StubAuthenticationService( - AuthenticateResult.Success(new AuthenticationTicket( - new System.Security.Claims.ClaimsPrincipal(new System.Security.Claims.ClaimsIdentity("Cookies")), - authenticationProperties, - CookieAuthenticationDefaults.AuthenticationScheme))); - httpContext.RequestServices = new ServiceCollection() - .AddSingleton(authenticationService) - .BuildServiceProvider(); + new Claim(OidcUserAccessTokenProvider.AccessTokenClaimType, "user-access-token") + }, "TestAuthentication")); var provider = new OidcUserAccessTokenProvider( - new HttpContextAccessor { HttpContext = httpContext }); + new StubAuthenticationStateProvider(principal)); var result = await provider.GetAccessTokenAsync(CancellationToken.None); @@ -38,42 +26,24 @@ public async Task GetAccessTokenAsync_ReturnsAccessTokenFromAuthenticatedUserSes [Fact] public async Task GetAccessTokenAsync_WithoutAuthenticatedSession_ReturnsFailure() { - var httpContext = new DefaultHttpContext - { - RequestServices = new ServiceCollection() - .AddSingleton(new StubAuthenticationService(AuthenticateResult.NoResult())) - .BuildServiceProvider() - }; var provider = new OidcUserAccessTokenProvider( - new HttpContextAccessor { HttpContext = httpContext }); + new StubAuthenticationStateProvider(new ClaimsPrincipal(new ClaimsIdentity()))); var result = await provider.GetAccessTokenAsync(CancellationToken.None); result.IsFailed.ShouldBeTrue(); } - private sealed class StubAuthenticationService : IAuthenticationService + private sealed class StubAuthenticationStateProvider : AuthenticationStateProvider { - public AuthenticateResult Result { get; } + private readonly ClaimsPrincipal Principal; - public StubAuthenticationService(AuthenticateResult result) + public StubAuthenticationStateProvider(ClaimsPrincipal principal) { - this.Result = result; + this.Principal = principal; } - public Task AuthenticateAsync(HttpContext context, string? scheme) - => Task.FromResult(this.Result); - - public Task ChallengeAsync(HttpContext context, string? scheme, AuthenticationProperties? properties) - => Task.CompletedTask; - - public Task ForbidAsync(HttpContext context, string? scheme, AuthenticationProperties? properties) - => Task.CompletedTask; - - public Task SignInAsync(HttpContext context, string? scheme, System.Security.Claims.ClaimsPrincipal principal, AuthenticationProperties? properties) - => Task.CompletedTask; - - public Task SignOutAsync(HttpContext context, string? scheme, AuthenticationProperties? properties) - => Task.CompletedTask; + public override Task GetAuthenticationStateAsync() + => Task.FromResult(new AuthenticationState(this.Principal)); } } diff --git a/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs b/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs index 65e03454..ba3beeb7 100644 --- a/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs +++ b/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs @@ -1,40 +1,31 @@ using EstateManagementUI.BusinessLogic.Client; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Components.Authorization; using SimpleResults; namespace EstateManagementUI.BlazorServer.Authentication; public sealed class OidcUserAccessTokenProvider : IUserAccessTokenProvider { - private readonly IHttpContextAccessor HttpContextAccessor; + public const string AccessTokenClaimType = "estate_management_access_token"; - public OidcUserAccessTokenProvider(IHttpContextAccessor httpContextAccessor) + private readonly AuthenticationStateProvider AuthenticationStateProvider; + + public OidcUserAccessTokenProvider(AuthenticationStateProvider authenticationStateProvider) { - this.HttpContextAccessor = httpContextAccessor; + this.AuthenticationStateProvider = authenticationStateProvider; } public async Task> GetAccessTokenAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var httpContext = this.HttpContextAccessor.HttpContext; - if (httpContext is null) - return Result.Failure("No active HTTP context is available."); - - var authenticationService = httpContext.RequestServices.GetRequiredService(); - var authenticationResult = await authenticationService.AuthenticateAsync( - httpContext, - CookieAuthenticationDefaults.AuthenticationScheme); - - if (!authenticationResult.Succeeded || authenticationResult.Properties is null) + var authenticationState = await this.AuthenticationStateProvider.GetAuthenticationStateAsync(); + if (authenticationState.User.Identity?.IsAuthenticated != true) return Result.Failure("The current user is not authenticated."); - var accessToken = authenticationResult.Properties.GetTokens() - .SingleOrDefault(token => token.Name == "access_token")?.Value; + var accessToken = authenticationState.User.FindFirst(AccessTokenClaimType)?.Value; if (String.IsNullOrWhiteSpace(accessToken)) - return Result.Failure("The current user session does not contain an access token."); + return Result.Failure("The current user authentication state does not contain an access token."); return Result.Success(accessToken); } diff --git a/EstateManagementUI.BlazorServer/Common/BoostrapperExtensions.cs b/EstateManagementUI.BlazorServer/Common/BoostrapperExtensions.cs index 76bd24f3..6d63ecd2 100644 --- a/EstateManagementUI.BlazorServer/Common/BoostrapperExtensions.cs +++ b/EstateManagementUI.BlazorServer/Common/BoostrapperExtensions.cs @@ -20,6 +20,7 @@ using System.Data.SqlTypes; using System.Net; using System.Reflection; +using System.Security.Claims; using System.Security.Cryptography.X509Certificates; using TransactionProcessor.Client; @@ -176,7 +177,7 @@ public static WebApplicationBuilder ConfigureLiveAuthentication(this WebApplicat options.Authority = authorityAddress; options.ClientId = builder.Configuration["Authentication:ClientId"]; options.ClientSecret = builder.Configuration["Authentication:ClientSecret"]; - options.ResponseType = "code"; + options.ResponseType = "code id_token"; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; @@ -211,6 +212,16 @@ public static WebApplicationBuilder ConfigureLiveAuthentication(this WebApplicat // Handle prompt parameter for forcing re-authentication options.Events = new OpenIdConnectEvents { + OnTicketReceived = context => + { + var accessToken = context.Properties.GetTokenValue("access_token"); + if (!String.IsNullOrWhiteSpace(accessToken) && context.Principal?.Identity is ClaimsIdentity identity) + { + identity.AddClaim(new Claim(OidcUserAccessTokenProvider.AccessTokenClaimType, accessToken)); + } + + return Task.CompletedTask; + }, OnRedirectToIdentityProvider = context => { context.ProtocolMessage.IssuerAddress = $"{issuerAddress}/connect/authorize"; @@ -248,24 +259,24 @@ public static WebApplicationBuilder RegisterPermissionServices(this WebApplicati public static WebApplicationBuilder RegisterProductionMeriator(this WebApplicationBuilder builder) { builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(EstateRequestHandler).Assembly)); - builder.Services.AddSingleton(); + builder.Services.AddScoped(); builder.Services.AddSingleton>(container => (serviceName) => ConfigurationReader.GetBaseServerUri(serviceName).OriginalString); return builder; } public static WebApplicationBuilder RegisterUIServices(this WebApplicationBuilder builder) { - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); return builder; } public static WebApplicationBuilder RegisterClients(this WebApplicationBuilder builder) { - builder.Services.AddSingleton(); + builder.Services.AddScoped(); builder.Services.RegisterHttpClient(); builder.Services.RegisterHttpClient(); builder.Services.RegisterHttpClient(); diff --git a/EstateManagementUI.BlazorServer/Common/TestAuthenticationHandler.cs b/EstateManagementUI.BlazorServer/Common/TestAuthenticationHandler.cs index 6f65ce14..2f676ab6 100644 --- a/EstateManagementUI.BlazorServer/Common/TestAuthenticationHandler.cs +++ b/EstateManagementUI.BlazorServer/Common/TestAuthenticationHandler.cs @@ -1,4 +1,5 @@ using System.Globalization; +using EstateManagementUI.BlazorServer.Authentication; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; using System.Security.Claims; @@ -78,6 +79,7 @@ protected override Task HandleAuthenticateAsync() new Claim(ClaimTypes.Name, userName), new Claim(ClaimTypes.Email, $"{roleName.ToLower(CultureInfo.CurrentCulture)}@test.com"), new Claim("estateId", "11111111-1111-1111-1111-111111111111"), + new Claim(OidcUserAccessTokenProvider.AccessTokenClaimType, "test-user-access-token"), new Claim(ClaimTypes.Role, roleName), new Claim("role", roleName) }; diff --git a/EstateManagementUI.BlazorServer/TokenManagement/AutomaticTokenManagementCookieEvents.cs b/EstateManagementUI.BlazorServer/TokenManagement/AutomaticTokenManagementCookieEvents.cs index 85b6d22b..a5e6f800 100644 --- a/EstateManagementUI.BlazorServer/TokenManagement/AutomaticTokenManagementCookieEvents.cs +++ b/EstateManagementUI.BlazorServer/TokenManagement/AutomaticTokenManagementCookieEvents.cs @@ -1,5 +1,6 @@ namespace EstateManagementUI.BlazorServer.TokenManagement { + using EstateManagementUI.BlazorServer.Authentication; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -90,6 +91,17 @@ public override async Task ValidatePrincipal(CookieValidatePrincipalContext cont context.Properties.UpdateTokenValue("access_token", response.AccessToken); context.Properties.UpdateTokenValue("refresh_token", response.RefreshToken); + if (context.Principal?.Identity is System.Security.Claims.ClaimsIdentity identity) + { + var existingAccessTokenClaim = identity.FindFirst(OidcUserAccessTokenProvider.AccessTokenClaimType); + if (existingAccessTokenClaim is not null) + { + identity.RemoveClaim(existingAccessTokenClaim); + } + + identity.AddClaim(new System.Security.Claims.Claim(OidcUserAccessTokenProvider.AccessTokenClaimType, response.AccessToken)); + } + DateTime newExpiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn); context.Properties.UpdateTokenValue("expires_at", newExpiresAt.ToString("o", CultureInfo.InvariantCulture)); From 3eba9f0cfaa01f06f0bdd0cb515e6a5584924e37 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Sat, 3 Oct 2026 16:04:42 +0100 Subject: [PATCH 3/3] Satisfy Codacy access token property rule --- .../Authentication/OidcUserAccessTokenProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs b/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs index ba3beeb7..07a2f4fb 100644 --- a/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs +++ b/EstateManagementUI.BlazorServer/Authentication/OidcUserAccessTokenProvider.cs @@ -6,7 +6,7 @@ namespace EstateManagementUI.BlazorServer.Authentication; public sealed class OidcUserAccessTokenProvider : IUserAccessTokenProvider { - public const string AccessTokenClaimType = "estate_management_access_token"; + public static string AccessTokenClaimType => "estate_management_access_token"; private readonly AuthenticationStateProvider AuthenticationStateProvider;