- ABP Framework version: v8.2.1
- UI Type: Blazor WASM
- Database System: EF Core (SQL Server)
- Tiered (for MVC) or Auth Server Separated (for Angular):
- Exception message and full stack trace:
- Steps to reproduce the issue:
Good morning,
I have a Blazor WASM project where I inserted a custom component in the toolbar, following this guide from the doc: https://abp.io/docs/latest/framework/ui/blazor/toolbars#example-add-a-notification-icon
The content of my custom component must be visible only after logging in and with a specific permission, because I need to make API calls when initializing the component, so I need to manage authentication and authorization. To do this I managed everything using the tag "AuthorizeView" (with Policy parameter as explained in the doc) and in the .razor.cs using the "CurrentUser.IsAuthenticated" and the "AuthorizationService.IsGrantedAsync...." inside the "OnInitializedAsync" method. My component code is as follows:
@using AbpSolution1.Books
@using AbpSolution1.Permissions
@using Microsoft.AspNetCore.Authorization
@inherits Volo.Abp.AspNetCore.Components.AbpComponentBase
@inject IBooksAppService BooksAppService
@* https://abp.io/docs/latest/framework/ui/blazor/authorization *@
<AuthorizeView Policy="@AbpSolution1Permissions.Books.Default">
<Authorized>
@if (IsAnyBook)
{
<div class="bg-success w-100 d-flex justify-content-center align-items-center h-100">
<b class="text-dark" style="font-size: 20px;">
Books available
</b>
</div>
}
else
{
<div class="bg-warning w-100 d-flex justify-content-center align-items-center h-100">
<b class="text-dark" style="font-size: 20px;">
No Books
</b>
</div>
}
</Authorized>
<NotAuthorized>
<div class="bg-danger w-100 d-flex justify-content-center align-items-center h-100">
<b class="text-dark" style="font-size: 20px;">
Not Authorized
</b>
</div>
</NotAuthorized>
</AuthorizeView>
@code {
private bool IsAnyBook { get; set; } = false;
private async Task LoadBooks()
{
try
{
GetBooksInput input = new GetBooksInput();
var books = await BooksAppService.GetListAsync(input);
IsAnyBook = books.Items.Any();
}
catch(Exception ex)
{
await HandleErrorAsync(ex);
Console.WriteLine(ex.Message);
}
}
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
var isAutheticated = CurrentUser.IsAuthenticated;
var isAuthorized = await AuthorizationService.IsGrantedAsync(AbpSolution1Permissions.Books.Default);
if (isAutheticated && isAuthorized)
{
await LoadBooks();
}
}
}
However, when I log in and the component is rendered I get a 401 "Unauthorized" error, even if the logged in user has permission to make the call. It would appear that the call is made before obtaining the token.
How can I solve it? (if necessary we can attach the entire zipped project)
Thank you, best regards
Roberto
36 Answer(s)
-
0
hi
You can use
@attribute [Authorize(AbpSolution1Permissions.Books.Default)]
in your component.eg https://github.com/abpframework/abp/blob/dev/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor#L2
-
0
Not work.
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-8.0#authorize-attribute Only use [Authorize] on @page components reached via the Blazor router. Authorization is only performed as an aspect of routing and not for child components rendered within a page. To authorize the display of specific parts within a page, use AuthorizeView instead.
-
0
hi
You can try to inject the
ICurrentUser
to check theCurrentUser.IsAuthenticated
@if (CurrentUser.IsAuthenticated) { }
And
IAuthorizationService
to check the permission.https://abp.io/docs/latest/framework/ui/blazor/authorization?_redirected=B8ABF606AA1BDF5C629883DF1061649A#iauthorizationservice
-
0
I already do it, did you read the code in the ticket?
protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); var isAutheticated = CurrentUser.IsAuthenticated; var isAuthorized = await AuthorizationService.IsGrantedAsync(AbpSolution1Permissions.Books.Default); if (isAutheticated && isAuthorized) { await LoadBooks(); } }
-
0
hi
I mean, you can remove the
<AuthorizeView
codes.@if (IsAnyBook) { <div class="bg-success w-100 d-flex justify-content-center align-items-center h-100"> <b class="text-dark" style="font-size: 20px;"> Books available </b> </div> } else { <div class="bg-warning w-100 d-flex justify-content-center align-items-center h-100"> <b class="text-dark" style="font-size: 20px;"> No Books </b> </div> }
-
0
From the logs I see that after the login a call to Books is made correctly, then UserInfo is called and this seems to change the state of the page so the component present in the toolbar is reloaded (reinitialized) only that in that case it sees the user logged in with the correct permissions but the next call fails for expired token
In case of a 401 error, apparently, the Client Proxy error handling it and the user is disconnected and sent back to the login session.
-
0
hi
Can you share a simple project to reproduce this?
I will download and check it.
liming.ma@volosoft.com
Thanks.
-
0
Project sent via wetransfer. Thanks
-
0
Thanks I will check it asap.
-
0
hi
It works with the code below.
@using AbpSolution1.Books @using AbpSolution1.Permissions @using Microsoft.AspNetCore.Authorization @inherits Volo.Abp.AspNetCore.Components.AbpComponentBase @inject IBooksAppService BooksAppService @if(IsAuthorized) { if (IsAnyBook) { <div class="bg-success w-100 d-flex justify-content-center align-items-center h-100"> <b class="text-dark"> Books available </b> </div> } else { <div class="bg-warning w-100 d-flex justify-content-center align-items-center h-100"> <b class="text-dark"> No Books </b> </div> } } else { <div class="bg-danger w-100 d-flex justify-content-center align-items-center h-100"> <b class="text-dark"> Not Authorized </b> </div> } @code { private bool IsAuthorized { get; set; } = false; private bool IsAnyBook { get; set; } = false; private async Task LoadBooks() { try { GetBooksInput input = new GetBooksInput(); var books = await BooksAppService.GetListAsync(input); IsAnyBook = books.Items.Any(); } catch(Exception ex) { await HandleErrorAsync(ex); Console.WriteLine(ex.Message); } } protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); var isAutheticated = CurrentUser.IsAuthenticated; IsAuthorized = await AuthorizationService.IsGrantedAsync(AbpSolution1Permissions.Books.Default); if (isAutheticated && IsAuthorized) { await LoadBooks(); } } }
-
0
I debug from Visual Studio with multi-launch project using IIS Express and then log in to the portal. I don't understand why the component is initialized twice and the second time the get passes a wrong token to the api It looks like a token handling issue because the component is in the layout instead of being on a page.
I get the same error if I publish on IIS
Can you try again by also logging out and logging back in? Maybe it's a problem that happens if you already have a token in cache
Thanks
-
0
hi
Please set the log level to debug and reproduce the error, then share the logs.
Thanks
liming.ma@volosoft.com
public class Program { public async static Task<int> Main(string[] args) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) .WriteTo.Async(c => c.Console()) .CreateLogger();
-
0
I have sent the requested log Thanks
-
0
Thanks. I will check it asap.
-
0
hi
I still can't reproduce your error but you can try code below:
PostConfigureServices
using System; using System.Linq; using System.Net.Http; using AbpSolution1.Blazor.Client.Navigation; using AbpSolution1.Localization; using Blazorise.Bootstrap5; using Blazorise.Icons.FontAwesome; using Localization.Resources.AbpUi; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using OpenIddict.Abstractions; using Volo.Abp.Account.Pro.Admin.Blazor.WebAssembly; using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; using Volo.Abp.AspNetCore.Components.Web.Theming.Toolbars; using Volo.Abp.AspNetCore.Components.WebAssembly; using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXTheme; using Volo.Abp.AuditLogging.Blazor.WebAssembly; using Volo.Abp.Autofac.WebAssembly; using Volo.Abp.AutoMapper; using Volo.Abp.Identity.Pro.Blazor.Server.WebAssembly; using Volo.Abp.LanguageManagement.Blazor.WebAssembly; using Volo.Abp.LeptonX.Shared; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.OpenIddict.Pro.Blazor.WebAssembly; using Volo.Abp.SettingManagement.Blazor.WebAssembly; using Volo.Abp.TextTemplateManagement.Blazor.WebAssembly; using Volo.Abp.UI.Navigation; using Volo.Chat.Blazor.WebAssembly; namespace AbpSolution1.Blazor.Client; [DependsOn( typeof(AbpSettingManagementBlazorWebAssemblyModule), typeof(AbpAutofacWebAssemblyModule), typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXThemeModule), typeof(AbpAccountAdminBlazorWebAssemblyModule), typeof(AbpIdentityProBlazorWebAssemblyModule), typeof(ChatBlazorWebAssemblyModule), typeof(AbpOpenIddictProBlazorWebAssemblyModule), typeof(AbpAuditLoggingBlazorWebAssemblyModule), typeof(TextTemplateManagementBlazorWebAssemblyModule), typeof(LanguageManagementBlazorWebAssemblyModule), typeof(AbpSolution1HttpApiClientModule) )] public class AbpSolution1BlazorClientModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { var environment = context.Services.GetSingletonInstance<IWebAssemblyHostEnvironment>(); var builder = context.Services.GetSingletonInstance<WebAssemblyHostBuilder>(); ConfigureLocalization(); ConfigureAuthentication(builder); ConfigureHttpClient(context, environment); ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); ConfigureAutoMapper(context); ConfigureTheme(); ConfigureToolbar(); } private void ConfigureToolbar() { Configure<AbpToolbarOptions>(options => { options.Contributors.Add(new MyToolbarContributor()); }); } private void ConfigureLocalization() { Configure<AbpLocalizationOptions>(options => { options.Resources .Get<AbpSolution1Resource>() .AddBaseTypes(typeof(AbpUiResource)); }); } private void ConfigureTheme() { Configure<LeptonXThemeOptions>(options => { options.DefaultStyle = LeptonXStyleNames.System; }); Configure<LeptonXThemeBlazorOptions>(options => { // When this is changed, `AbpCli:Bundle:LeptonXTheme.Layout` parameter with value 'top-menu' should be added into appsettings.json, // Then `abp bundle` command should be executed to apply the changes. options.Layout = LeptonXBlazorLayouts.SideMenu; }); } private void ConfigureRouter(ServiceConfigurationContext context) { Configure<AbpRouterOptions>(options => { options.AppAssembly = typeof(AbpSolution1BlazorClientModule).Assembly; }); } private void ConfigureMenu(ServiceConfigurationContext context) { Configure<AbpNavigationOptions>(options => { options.MenuContributors.Add(new AbpSolution1MenuContributor(context.Services.GetConfiguration())); }); } private void ConfigureBlazorise(ServiceConfigurationContext context) { context.Services .AddBootstrap5Providers() .AddFontAwesomeIcons(); } private static void ConfigureAuthentication(WebAssemblyHostBuilder builder) { builder.Services.AddOidcAuthentication(options => { builder.Configuration.Bind("AuthServer", options.ProviderOptions); options.UserOptions.NameClaim = OpenIddictConstants.Claims.Name; options.UserOptions.RoleClaim = OpenIddictConstants.Claims.Role; options.ProviderOptions.DefaultScopes.Add("AbpSolution1"); options.ProviderOptions.DefaultScopes.Add("roles"); options.ProviderOptions.DefaultScopes.Add("email"); options.ProviderOptions.DefaultScopes.Add("phone"); }); } private static void ConfigureHttpClient(ServiceConfigurationContext context, IWebAssemblyHostEnvironment environment) { context.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(environment.BaseAddress) }); } private void ConfigureAutoMapper(ServiceConfigurationContext context) { Configure<AbpAutoMapperOptions>(options => { options.AddMaps<AbpSolution1BlazorClientModule>(); }); } public override void PostConfigureServices(ServiceConfigurationContext context) { var msAuthenticationStateProvider = context.Services.FirstOrDefault(x => x.ServiceType == typeof(AuthenticationStateProvider)); if (msAuthenticationStateProvider is {ImplementationType: not null} && msAuthenticationStateProvider.ImplementationType.IsGenericType && msAuthenticationStateProvider.ImplementationType.GetGenericTypeDefinition() == typeof(WebAssemblyAuthenticationStateProvider<,,>)) { var implementationType = typeof(RemoteAuthenticationService<,,>).MakeGenericType( msAuthenticationStateProvider.ImplementationType.GenericTypeArguments[0], msAuthenticationStateProvider.ImplementationType.GenericTypeArguments[1], msAuthenticationStateProvider.ImplementationType.GenericTypeArguments[2]); context.Services.Replace(ServiceDescriptor.Scoped(typeof(AuthenticationStateProvider), implementationType)); } } }
-
0
I sent you the logs from today's test. I tried without the fix and got the error. I added the fix and I don't get the error anymore. Can you explain what that piece of code does? Will this fix be a patch in the next release? Is this fix also compatible with AzureAd login?
Thanks, Roberto
-
0
hi
I will investigate this error. It only happened in your environment.
This fix will not break anything. I will reply here if I get any results.
-
0
Ok, but can you explain what that piece of code does?
-
0
hi
Blazor requests a new token each time it is initialized/refreshed. The
WebAssemblyAuthenticationStateProvider
class is used to revoke a token that is no longer in use.In your environment, it seems that the token was revoked in advance.
Can you share the request info for these two requests? (headers, post body)
Thanks.
-
0
I sent you the logs of browser.
thanks
-
0
I will find the reason as soon as possible, thanks
-
0
https://github.com/abpframework/abp/issues/20483
Hi, do you think this issue could be related to my problem?
-
0
No, I will reply to you with detailed reasons after I solve it.
-
0
hi
Can you share an online URL to reproduce the problem?
-
0
The solution is published on an on-premise server that is not accessible externally from our client's network. I try to create an environment on Azure and then I'll send you the link