Open Closed

Toolbar custom component and Authorization #7675


User avatar
1
roberto.fiocchi created
  • 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)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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

  • User Avatar
    0
    roberto.fiocchi created

    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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can try to inject the ICurrentUser to check the CurrentUser.IsAuthenticated

    @if (CurrentUser.IsAuthenticated)
    {
    
    }
    

    And IAuthorizationService to check the permission.

    https://abp.io/docs/latest/framework/ui/blazor/authorization?_redirected=B8ABF606AA1BDF5C629883DF1061649A#iauthorizationservice

  • User Avatar
    0
    roberto.fiocchi created

    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();
        }
    }
    
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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>
    }
    
  • User Avatar
    0
    roberto.fiocchi created

    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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you share a simple project to reproduce this?

    I will download and check it.

    liming.ma@volosoft.com

    Thanks.

  • User Avatar
    0
    roberto.fiocchi created

    Project sent via wetransfer. Thanks

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Thanks I will check it asap.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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();
            }
        }
    }
    
    
    
    

  • User Avatar
    0
    roberto.fiocchi created

    Not worked for me.

    Error:

    In console I see a warning:

    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

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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();
    
    
  • User Avatar
    0
    roberto.fiocchi created

    I have sent the requested log Thanks

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Thanks. I will check it asap.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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));
            }
        }
    }
    
    
  • User Avatar
    0
    roberto.fiocchi created

    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

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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.

  • User Avatar
    0
    roberto.fiocchi created

    Ok, but can you explain what that piece of code does?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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.

  • User Avatar
    0
    roberto.fiocchi created

    I sent you the logs of browser.

    thanks

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    I will find the reason as soon as possible, thanks

  • User Avatar
    0
    roberto.fiocchi created

    https://github.com/abpframework/abp/issues/20483

    Hi, do you think this issue could be related to my problem?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    No, I will reply to you with detailed reasons after I solve it.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you share an online URL to reproduce the problem?

  • User Avatar
    0
    roberto.fiocchi created

    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

Made with ❤️ on ABP v9.1.0-preview. Updated on November 19, 2024, 12:56