Activities of "maliming"

Good news. 👍

Hi,

<AbpScripts>'s @rendermode doesn't have to follow the page's. Decouple them on the WASM-only route only, and you keep the page on prerender: false (no /_blazor circuit) while letting <AbpScripts> server-prerender the bundle into the initial HTML.

<AbpScripts BundleName="@BlazorLeptonXThemeBundles.Scripts.Global"
            WebAssemblyScriptFiles="GlobalScripts"
            @rendermode="AbpScriptsRenderMode" />

@code {
    // Page render mode — unchanged.
    private IComponentRenderMode PageRenderMode =>
        IsWasmOnlyRoute ? new InteractiveWebAssemblyRenderMode(prerender: false)
        : IsAutoRoute   ? InteractiveAuto
                        : InteractiveServer;

    // AbpScripts — only the WASM-only branch differs: prerender on the server,
    // activate in WASM (no SignalR), so the bundle lands in the initial HTML.
    private IComponentRenderMode AbpScriptsRenderMode =>
        IsWasmOnlyRoute ? InteractiveWebAssembly   // prerender: true by default
        : PageRenderMode;                          // Auto / Server unchanged
}

InteractiveWebAssembly here is static SSR prerender + WASM activation; it doesn't open a SignalR circuit. The server prerender resolves BundleName, whose BlazorStandardBundles.Scripts.Global base contributes abp.js via BlazorGlobalScriptContributor, so window.abp is defined before WASM activates and SideMenuLayout reads abp.utils.getCookieValue. Auto and Server routes are untouched.

Verified on a fresh Blazor WebApp + LeptonX + Blazorise template (the same wiring you have) with a page on InteractiveWebAssemblyRenderMode(prerender: false):

| <AbpScripts> @rendermode | Bundle in initial HTML | /_blazor circuit | window.abp on page render | JSException | |---|---|---|---|---| | InteractiveAuto (default) | yes (Blazor.LeptonXTheme.Global.<hash>.js) | opened (negotiate?negotiateVersion=1) | ready | none | | InteractiveWebAssemblyRenderMode(prerender: false) (current) | no | not opened | undefined | reproduced (Could not find 'abp.utils.removeOidcUser' ('abp' was undefined)) | | InteractiveWebAssembly (prerender: true, suggested) | yes (Blazor.LeptonXTheme.Global.<hash>.js) | not opened | ready | none |

Could you give the AbpScriptsRenderMode split a try and let us know whether the JSException window goes away?

Thanks

Hi,

Let me walk through this in two parts: route-based tenant resolution and per-tenant external providers. Both are doable with the existing Account Pro and OpenIddict modules.

1) Resolving the tenant from a route segment like /tenant1

ABP has a built-in RouteTenantResolveContributor that reads the {__tenant} route value. The non-trivial part is getting {__tenant} to apply to every route in the AuthServer (Razor Pages, controllers) and making links/redirects keep the prefix.

I wrote a community article that covers the full setup — route conventions for pages and controllers, a PathBase middleware so generated links automatically include /tenantX, a custom RouteTenantResolveContributor that reads the tenant from PathBase, and AbpThemingOptions.BaseUrl so the client-side abp.appPath is correct:

https://abp.io/community/articles/Resolving-Tenant-from-Route-in-ABP-Framework/ah7oru97

The AuthServer is just a Razor Pages app under the hood, so the same conventions apply to /Account/Login, /Account/Register, the OpenIddict authorize endpoints, etc. Once the article's pieces are wired up, hitting https://portal.auth.com/tenant1/Account/Login resolves tenant1 automatically and the context stays "fixed" for the rest of the request chain — subsequent links and cookies stay under /tenant1.

If you also want to limit resolution to only the route (so cookies/headers can't override the tenant), you can configure AbpTenantResolveOptions and clear the other contributors:

Configure<AbpTenantResolveOptions>(options =>
{
    options.TenantResolvers.Clear();
    options.TenantResolvers.Add(new MyRouteTenantResolveContributor());
});

2) Per-tenant external authentication providers (Entra ID, Facebook, Google, …)

The Account Pro module already supports this scenario out of the box — each tenant can enable/disable an external provider and configure its own ClientId/ClientSecret/Authority from the Settings page. You don't need to write a custom dynamic options provider for the built-in schemes; the module wires IOptionsMonitor<TOptions> per tenant for you.

The pattern is: call the standard .AddXxx(scheme, …) and chain .WithDynamicOptions<TOptions, THandler>(scheme, …) to mark which option properties should come from the per-tenant settings.

Documentation: https://abp.io/docs/latest/modules/account-pro#sociallong-external-logins

For Entra ID, use OpenIdConnect. Here's the full sample from the docs (works the same way for any OIDC provider — different ClientId / Authority / ClientSecret per tenant):

context.Services.AddAuthentication()
    .AddOpenIdConnect("AzureOpenId", "Azure AD", options =>
    {
        options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
        options.RequireHttpsMetadata = false;
        options.SaveTokens = true;
        options.GetClaimsFromUserInfoEndpoint = true;
        options.Scope.Add("email");
        options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
        options.CallbackPath = configuration["AzureAd:CallbackPath"];
    })
    .WithDynamicOptions<OpenIdConnectOptions, OpenIdConnectHandler>("AzureOpenId",
        options =>
        {
            options.WithProperty(x => x.Authority);
            options.WithProperty(x => x.ClientId);
            options.WithProperty(x => x.ClientSecret, isSecret: true);
        });

// OIDC needs metadata to be re-resolved when Authority changes
public class OpenIdConnectPostConfigureAccountExternalProviderOptions
    : IPostConfigureAccountExternalProviderOptions<OpenIdConnectOptions>
{
    private readonly IEnumerable<IPostConfigureOptions<OpenIdConnectOptions>> _postConfigureOptions;

    public OpenIdConnectPostConfigureAccountExternalProviderOptions(
        IEnumerable<IPostConfigureOptions<OpenIdConnectOptions>> postConfigureOptions)
    {
        _postConfigureOptions = postConfigureOptions;
    }

    public Task PostConfigureAsync(string name, OpenIdConnectOptions options)
    {
        foreach (var configureOption in _postConfigureOptions)
        {
            configureOption.PostConfigure(name, options);
        }
        return Task.CompletedTask;
    }
}

context.Services.TryAddEnumerable(
    ServiceDescriptor.Singleton<
        IPostConfigureAccountExternalProviderOptions<OpenIdConnectOptions>,
        OpenIdConnectPostConfigureAccountExternalProviderOptions>());

After this, an admin signs in to the host, switches the management UI to the target tenant, and fills in Authority / ClientId / ClientSecret per tenant on the External Providers settings page. The login page automatically shows only the providers enabled for the current tenant — so combined with part 1, a user landing on /tenant1/Account/Login only sees Entra + MyID, /tenant2 only sees Challenge, etc.

The template already wires Google / Microsoft / Twitter this way — see MyProjectNameAuthServerModule.ConfigureExternalProviders(...) in your AuthServer project as a working reference.

3) Tiered solution — important

Because you have a separated AuthServer + Blazor host (tiered), both projects need the .WithDynamicOptions<…>(…) registration. The AuthServer runs the actual login/challenge flow, but the host also needs the dynamic registration so external login callbacks resolve correctly. You can copy the same .WithDynamicOptions<…>(…) block into the host module without the static .AddOpenIdConnect(…) call (the host doesn't need to challenge directly).

4) MyID / Challenge

If "MyID" is an OIDC-compliant IdP, the snippet above works as-is — register one more scheme like .AddOpenIdConnect("MyID", …).WithDynamicOptions<OpenIdConnectOptions, OpenIdConnectHandler>("MyID", …). Could you share more details about what the "Challenge" and "MyID" providers actually are (OAuth 2.0? OIDC? SAML? a custom protocol?) so we can suggest the right approach for those two specifically?

Thanks

Hi,

We can't reproduce the JSException window on a default Blazor WebApp + LeptonX template — would you mind sharing some details so we can spot what's different in your setup?

For reference, the default template wires the bundle like this:

// App.razor — Blazor host project
<AbpScripts BundleName="@BlazorMudBlazorLeptonXThemeBundles.Scripts.Global"
            WebAssemblyScriptFiles="GlobalScripts"
            @rendermode="InteractiveAuto" />

@code {
    private List<string> GlobalScripts => ["global.js"];
}
// BlazorModule.cs
Configure<AbpBundlingOptions>(options =>
{
    options.Parameters.InteractiveAuto = true;
    // style + script bundle contributors ...
});

With this setup, the server-side bundle (resolved from BundleName) is rendered into the initial HTML during the prerender pass. That bundle inherits from BlazorStandardBundles.Scripts.Global, which already includes abp.js via BlazorGlobalScriptContributor, so window.abp is defined before the WebAssembly runtime activates and SideMenuLayout reads abp.utils.getCookieValue. No race window.

If @rendermode on <AbpScripts> is anything that skips server prerender (for example new InteractiveWebAssemblyRenderMode(prerender: false)), or Parameters.InteractiveAuto = true is missing from the host project's bundling options, the bundle that defines window.abp only lands in the DOM after WASM activates — and that gap is the JSException window you're seeing.

Could you share:

  1. Your Blazor host project's full App.razor (the file containing <AbpScripts>)
  2. The Configure<AbpBundlingOptions> block in your BlazorModule
  3. The exact abp new command (or abp-studio template options) you used to generate the project

That should let us pin down where your setup diverges from the default.

Thanks

Hi,

Thanks for the detailed write-up - having the symptoms already mapped to specific lib functions saves us a lot of digging.

A couple of these are real gaps in our OAuth strategy on the framework side - the silent tryLogin().catch(noop) and the missing ignoreCorsIssues on revokeTokenAndLogout - and I have those queued for a fix. But the nonce/PKCE race and the application-configuration timing don't trigger consistently in the default Angular template, so something specific to your setup is likely pushing them (SSR config, custom OAuthStorage, AuthServer CORS, browser storage policy, or how OpenIddict is configured to return the id_token / nonce).

Easiest path forward: could you create a minimal repro in a private GitHub repo and add maliming as a collaborator? Just enough to trigger the loop and the silent failures - your AuthServer + HttpApi.Host + Angular wired the way they currently are, with the same package versions. Once I have access I'll push the fixes directly to a branch there, so you can pull the actual changes instead of carrying monkey-patches across upgrades.

If a private repo doesn't fit, you can zip the project and send it via WeTransfer to liming.ma@volosoft.com - just strip any secrets / connection strings first.

Thanks

Hi,

The TS2307 happens because account-admin-config.module.ts resolves @abp/ng.core from inside ../modules/Volo.Abp.Account.Pro/angular/..., and there's no node_modules/@abp under that folder yet. The resolver walks up from there and never reaches your main app's node_modules.

The Angular template ships a script for exactly this case. Edit <your-app>/angular/scripts/symlink-config.ps1 and add the module's angular folder to $PackageDirectories:

$script:PackageDirectories = @(
    "../../modules/Volo.Abp.Account.Pro/angular"
)

Then from <your-app>/angular/scripts/, run:

pwsh ./setup-symlinks.ps1

You should see Linked @angular, Linked @abp, Linked @volo, Linked rxjs, Linked tslib printed, and the TS2307 will be gone. The default $PackagesToSymlink array already covers all the framework packages, so you only need to add the directory path.

One more thing: please remove the manual @abp/ng.core path mapping you added in your main tsconfig.json. After the symlinks are in place, Angular resolves @abp/ng.core from node_modules on its own — the manual mapping ends up competing with that and can lead to inconsistent resolution.

We'll fix this on our side so the path gets added and the symlinks get refreshed automatically when you Import a module or download a Pro module's source. It'll go out in the next ABP Studio release.

Thanks

Hi Karim,

Thanks for the detailed question. Let me address each concern.

1. The banner — your ABP license already covers Blazorise PRO.

You don't need to purchase a separate Blazorise license. Volosoft has an agreement with Megabit (Blazorise's vendor): every active ABP commercial license includes a Blazorise PRO license at no extra cost.

Please follow this walkthrough to claim and configure the Product Token:

https://abp.io/support/questions/9912/How-to-get-and-use-Blazorise-License-in-ABP-Projects

One thing worth flagging for Blazor WebApp solutions: register the token in both *BlazorModule and *BlazorClientModule. The WASM client won't read the server's appsettings.json, so a server-only configuration lets the banner appear once the client takes over from prerender.

2. About the licensing question if your ABP subscription lapses.

Let me check with our licensing team on this and get back to you with a clear answer.

3. MudBlazor support in v10.4.2 — an additional UI option.

In v10.4.2 we're adding MudBlazor as an additional supported UI alongside Blazorise. It's not a replacement — both stay supported, and you can pick what fits your project best. MudBlazor is MIT-licensed if that's a factor for you.

10.4.2 isn't released yet but will ship very soon. Here's a community write-up about it:

https://github.com/abpframework/abp/blob/dev/docs/en/Community-Articles/2026-06-09-mudblazor-in-abp-framework/post.md

Thanks

Hi,

Your workaround is the right one — editing symlink-config.ps1 and re-running the symlink script is the supported mechanism, so it will hold up across rebuilds.

Edit <your-app>/angular/scripts/symlink-config.ps1 and list each imported module:

$script:PackageDirectories = @(
    "../../modules/<your-module-name>/angular"
)

(One line per module.)

Then from <your-app>/angular/scripts/, run:

pwsh ./setup-symlinks.ps1

You should see Linked @angular, Linked rxjs, Linked tslib printed, and the Routes mismatch will be gone. The default $PackagesToSymlink array already covers all the standard packages, so you only need to add the directory path.

We'll fix this on our side so the entry gets added and the symlinks get refreshed automatically when you Import a module. It'll go out in the next ABP Studio release.

Thanks

Hi,

A few things to clear up first:

  • Scriban 7.2.x does not require .NET 9. The package targets net8.0 and netstandard2.0, so your .NET 8 runtime is fine.
  • You can't pin Scriban to 7.x in your csproj on ABP 8.2.3 — same TypeLoadException as your earlier ticket https://abp.io/support/questions/10600#answer-3a20a31b-6032-e62f-8b26-51cae51edaf0. Pinning to 6.x doesn't help either, the advisory covers <= 7.1.0.

In a default ABP setup the exploit is not reachable from outside. ABP's Scriban surface (email templates, report templates) is developer-authored and shipped through the Virtual File System — there's no public endpoint that lets end users submit template bodies. Even with the Volo.Abp.TextTemplateManagement module, editing template contents requires the TextTemplateManagement.TextTemplates.EditContents permission, which is admin-only by default. So if your app only renders templates and doesn't expose template editing to regular users, you're not actually exposed to this CVE — but you'll still see the NuGet audit warning because the package version is what gets scanned, not the call graph.

If you want to block the exploit at the engine level anyway (recommended if you do let any non-admin user influence template content), override ScribanTemplateRenderingEngine and remove the unbounded array.insert_at builtin:

using System.Collections.Generic;
using Microsoft.Extensions.Localization;
using Scriban;
using Scriban.Runtime;
using Volo.Abp.DependencyInjection;
using Volo.Abp.TextTemplating;
using Volo.Abp.TextTemplating.Scriban;

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(ScribanTemplateRenderingEngine))]
public class MyScribanTemplateRenderingEngine : ScribanTemplateRenderingEngine
{
    public MyScribanTemplateRenderingEngine(
        ITemplateDefinitionManager templateDefinitionManager,
        ITemplateContentProvider templateContentProvider,
        IStringLocalizerFactory stringLocalizerFactory)
        : base(templateDefinitionManager, templateContentProvider, stringLocalizerFactory)
    {
    }

    protected override TemplateContext CreateScribanTemplateContext(
        TemplateDefinition templateDefinition,
        Dictionary<string, object> globalContext,
        object? model = null)
    {
        var context = base.CreateScribanTemplateContext(templateDefinition, globalContext, model);
        if (context.BuiltinObject["array"] is ScriptObject arrayBuiltin)
        {
            arrayBuiltin.Remove("insert_at");
        }
        return context;
    }
}

After this, any template that tries array.insert_at throws ScriptRuntimeException: The function 'array.insert_at' was not found, while every other template keeps working unchanged.

To clear the SAST/NuGetAudit warning on top of that, add the suppression (requires the .NET 8.0.400 SDK or newer):

<ItemGroup>
  <NuGetAuditSuppress Include="https://github.com/advisories/GHSA-24c8-4792-22hx" />
</ItemGroup>

For the permanent fix we already bumped Scriban to 7.2.1 in the upcoming ABP 10.4 patch release (https://github.com/abpframework/abp/pull/25493). ABP 10.4 still targets net8.0, so picking it up is a framework upgrade, not a runtime one.

Thanks

Hi,

You're right — the native <input type="file"> button text is drawn by the browser itself, so the app's localization can't reach it. The Account Pro module should handle this rather than leaving the inconsistency for each project to work around.

We'll apply the same pattern you've already adopted in your Angular app: hide the native input, show a localized button (AbpAccount::ChoosePhoto) plus a read-only label that shows the selected file name or AbpAccount::NoFileChosen. The Angular profile-picture component and the MVC ProfilePicture view component will both get this treatment so the page is fully localized out of the box.

The fix is already prepared on our side and will land in an upcoming release.

Thanks

Showing 361 to 370 of 12914 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on November 02, 2026, 06:56
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.