In ABP Blazor, the login/profile/logout dropdown is not “automatic”; it is rendered by the active UI theme’s layout components (e.g., LeptonX/Basic theme). Concretely, the theme’s MainLayout (or header/topbar component used by that layout) includes a user menu/login display component in the header toolbar. If you replace the layout/topbar and don’t include that component (or an equivalent), the icon/menu will not show.
So yes: it is tied to the layout structure (MainLayout/TopBar/toolbar region). When integrating DevExpress templates, it’s very common to accidentally remove the theme’s header toolbar (or replace it with a new one) and therefore lose the user menu.
It is not primarily permission-based. The usual visibility logic is based on authentication state (e.g., rendered inside <AuthorizeView>: show user dropdown when authenticated, otherwise show Login link/button). Theme (LeptonX vs Basic) changes which component renders it, but the concept is the same.
What you must preserve (or re-add in your custom DevExpress layout):
<AuthorizeView>) and ABP services for user/tenant/menu if needed.If you are using LeptonX (Blazor), the official LeptonX Blazor UI docs list the layout and “Top Menu Components” involved. When you replace a LeptonX layout with a DevExpress layout, you typically need to carry over the topbar components (including the user menu component) into your new layout.
Links (validated):
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
TourOperatorClaimDestinationsHandler in the AuthServer project (the project that issues tokens), i.e. typically *.AuthServer in a tiered solution.
IAbpOpenIddictClaimsPrincipalHandler runs during token creation. Your API host (HttpApi.Host) only validates tokens; it doesn’t generate them.HttpApi.Host), then put it in *.HttpApi.Host.ConfigureServices of that module.MaxLength) automatically lights up UI only for UIs that are built to consume the “object extensions” metadata. The OpenIddict Application Management UI does not automatically render every extra property you add to OpenIddictApplication.If you want it visible/editable in Angular, you need to customize/replace the OpenIddict Application create/edit components (similar to how ABP suggests Angular component replacement for other module pages). There isn’t a built-in “automatic extra property rendering” for that OpenIddict page.
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Yes — using an extra property on OpenIddictApplication is a good place to store your Tour Operator code, and your ObjectExtensionManager.Instance.Modules().ConfigureOpenIddict(... ConfigureApplication ...) approach is valid for persisting that metadata.
But that alone does not automatically put it into the access token. You must do both:
AccessTokenBelow is a complete, ABP-supported way to do step (2).
Your extension config is fine for adding an extra property to the OpenIddict application entity.
Then you set the value for each client (Tour Operator) in /openiddict/Applications UI or via code.
Create a handler that ensures your custom claim is actually included in the access token (otherwise OpenIddict may drop it depending on destinations).
using System.Threading.Tasks;
using OpenIddict.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.OpenIddict;
public class TourOperatorClaimDestinationsHandler
: IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency
{
public Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context)
{
foreach (var claim in context.Principal.Claims)
{
if (claim.Type == ExtraPropertyConsts.TourOperatorCodePropertyName)
{
claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken);
}
}
return Task.CompletedTask;
}
}
Register it:
Configure<AbpOpenIddictClaimsPrincipalOptions>(options =>
{
options.ClaimsPrincipalHandlers.Add<TourOperatorClaimDestinationsHandler>();
});
You now need to add the claim value itself to the principal when the token is being created.
ABP’s OpenIddict docs recommend using claims principal handlers to control token claim destinations, but adding the claim value is typically done by contributing to the principal generation pipeline (implementation detail depends on whether you issue tokens for users or clients).
For client_credentials (machine-to-machine), you can add the claim during OpenIddict’s sign-in processing using an OpenIddict server event handler (this is also a known ABP approach in support answers for token claim customization).
Conceptually:
client_id)TourOperatorCode(Your storage choice—extra property on OpenIddictApplication—is fine; you’ll just need to load it via OpenIddict’s application manager or your own repository/table.)
Once the access token contains TourOperatorCode, you can filter:
var tourOperatorCode = CurrentUser.FindClaimValue(ExtraPropertyConsts.TourOperatorCodePropertyName);
// apply query restriction based on tourOperatorCode
This makes the filter non-bypassable (because it is derived from the authenticated client identity, not request parameters).
IAbpOpenIddictClaimsPrincipalHandler, destinations, registration)This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Store the TourOperatorId (or a “tourName”/code) as a property of the OpenIddict client record. Conceptually this is the right place because the client itself represents the external integration.
ABP doesn’t document a specific “TourOperatorId field” for OpenIddict apps, but you can persist custom metadata with the application (e.g., in your own table keyed by OpenIddictApplicationId, or using OpenIddict’s extensibility if you already use it).
ABP explicitly supports controlling which claims go into access_token / id_token by implementing IAbpOpenIddictClaimsPrincipalHandler and registering it via AbpOpenIddictClaimsPrincipalOptions. Use that hook to ensure your custom claim is included in the access token for M2M calls.
Implement a handler that:
tour_operator_id and sets its destination to AccessToken.This is aligned with ABP’s documented “Updating Claims In Access_token and Id_token” mechanism.
In your BlockspaceAppService (or better: a domain service used by it), read the claim from ICurrentUser / ClaimsPrincipal and apply it to the query.
Example idea (pseudo):
var tourOperatorId = CurrentUser.FindClaimValue("tour_operator_id");query = query.Where(x => x.TourOperators.Any(to => to.Id == tourOperatorIdGuid));
(or by tourName if that’s your invariant)This guarantees that even if the external caller tries different parameters, they cannot bypass the restriction.
ABP’s built-in tenant system is for multi-tenancy. For your case, treat it as a business authorization constraint:
No verified source in ABP docs specifically prescribes a dedicated “business partition filter” abstraction for this exact scenario; the verified, supported mechanism is adding claims to tokens and then enforcing them in application/domain logic.
IAbpOpenIddictClaimsPrincipalHandler)This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
@abp/ng.core / @abp/ng.oauth package source code from GitHub, because ABP Angular packages are distributed via npm and their full TypeScript sources are not published in the abpframework/abp GitHub repository in a way that SourceBot can retrieve here.@abp/ng.core. @abp/ng.core mainly contains abstractions/tokens, and the actual authentication/interceptor behavior is provided by @abp/ng.oauth (which overrides those abstractions). So the component that could be triggering logout/revoke behavior is much more likely in @abp/ng.oauth than @abp/ng.core.What you can do now (practical debugging path without core sources):
Confirm the initiator of /connect/revocation in DevTools
revocation request → check the Initiator tab (or “Stack trace” if available).main.js or a chunk file).Search in your built output for “revocation”
dist/ (or via DevTools global search), search for:
revocation/connect/revocationrevokeTokenlogoutPatch/override the logout redirect behavior to prevent full app refresh
If this is ABP Commercial and you need internal package sources
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
ABP Commercial doesn’t add a special “private_key_jwt” switch for external OIDC providers in Account Pro’s external login infrastructure. The documented AddOpenIdConnect(...).WithDynamicOptions(...) pattern exposes ClientSecret as a dynamic property, but it’s just a convenience layer over ASP.NET Core’s OpenIdConnectHandler and doesn’t inherently limit you to client-secret auth. There is no verified ABP doc/source that explicitly documents built-in private_key_jwt support for external OIDC token endpoint authentication.
Planned support: I can’t confirm any roadmap item from verified sources regarding adding first-class private_key_jwt support to WithDynamicOptions/Account Pro for external OIDC providers. (No verified source available for that.)
Recommended approach: implement it at the ASP.NET Core OpenIdConnect layer (events/backchannel) and keep ABP’s external-login flow unchanged.
AddOpenIdConnect(...).WithDynamicOptions for string properties like Authority, ClientId, etc.client_assertion / client_assertion_type instead of client_secret.Concretely, in ASP.NET Core OIDC, the token exchange happens inside the handler; the typical integration point is customizing OIDC events to adjust the token request sent to the token endpoint. Your goal is:
client_secretclient_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearerclient_assertion=<signed JWT>ABP-specific pitfalls to watch for (these are the ones that most often break ABP external login, regardless of private_key_jwt):
options.SignInScheme = Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme;
GetExternalLoginInfoAsync() can end up null or user mapping fails):options.ClaimActions.MapJsonKey(System.Security.Claims.ClaimTypes.NameIdentifier, "sub");
email for creating/registering external users. Request it if your provider doesn’t include it by default:options.Scope.Add("email");
options.GetClaimsFromUserInfoEndpoint = true;
Dynamic options: WithDynamicOptions is mainly for binding simple option properties (commonly strings). Your private key material/JWT signing logic typically won’t fit as a simple WithProperty(...) mapping; instead, keep the key in your own storage and use it inside your OIDC event to generate the assertion.
Interaction with OpenIddict / ABP auth server:
IdentityUserLogin record, then issue your own tokens/cookies as usual.IdentityConstants.ExternalScheme.Sources (validated):
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Volo.Abp.LanguageManagement.Domain is a commercial (Pro) module and it requires a valid ABP license at runtime. ABP-LIC-0017 - License code is not valid means the application is reading an AbpLicenseCode value, but ABP’s license validation rejects it (wrong/expired/mismatched license, or corrupted value).AbpLicenseCode (recommended via environment variable in containers), for example in docker-compose.yml:services:
vhs-language:
environment:
- AbpLicenseCode=PUT_YOUR_FULL_LICENSE_CODE_HERE
(Make sure it’s a single uninterrupted value; no extra spaces/quotes/newlines.)
AbpLicenseCode definitions (e.g., both appsettings.json and environment variables with different values).ABP-LIC-0017 after you re-paste the license code from the ABP customer portal/email (plain-text), this is a license/account issue and must be handled by ABP licensing support.(Verified: Language Management module requires a Team or higher license; your log also shows it’s Volo.Abp.LanguageManagement.Domain-v10.0.2.0 failing the license check.)
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
UserName input.UserName = EmailAddress before calling AccountAppService.RegisterAsync(...) so the backend always uses email as the username.Create (or override) the register page under your UI project:
Pages/Account/Register.cshtmlPages/Account/Register.cshtml.csIn Register.cshtml, remove the username field:
@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@model YourCompany.YourApp.Pages.Account.CustomRegisterModel
@inject IHtmlLocalizer<AccountResource> L
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<strong>
@L["AlreadyRegistered"]
<a href="@Url.Page("./Login", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})"
class="text-decoration-none">@L["Login"]</a>
</strong>
<form method="post" class="mt-4">
<abp-input asp-for="Input.EmailAddress" auto-focus="true" />
@if (!Model.IsExternalLogin)
{
<abp-input asp-for="Input.Password" />
}
<abp-button button-type="Primary" type="submit" class="btn-lg btn-block mt-4">
@L["Register"]
</abp-button>
</form>
</div>
</div>
Register.cshtml.cs, set username from email when registering.
Create a custom model inheriting ABP’s RegisterModel and override the registration method:using System.Threading.Tasks;
using Volo.Abp.Account;
using Volo.Abp.Account.Web.Pages.Account;
namespace YourCompany.YourApp.Pages.Account;
public class CustomRegisterModel : RegisterModel
{
public CustomRegisterModel(IAccountAppService accountAppService)
: base(accountAppService)
{
}
protected override async Task RegisterLocalUserAsync()
{
// Ensure the backend gets username even though UI doesn't ask for it:
Input.UserName = Input.EmailAddress;
await base.RegisterLocalUserAsync();
}
}
That’s enough to:
If you are using Blazor and still seeing the MVC register page, you can override the same page by adding it under your Blazor project:
YourApp.Blazor/Pages/Account/Register.cshtmlYourApp.Blazor/Pages/Account/Register.cshtml.csApply the same changes above (remove Input.UserName from the .cshtml, set Input.UserName = Input.EmailAddress in the model code-behind).
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
ABP’s LDAP integration (the Account module LDAP external login provider) is designed around one LDAP configuration per tenant (a single “current” LDAP settings set). There is no built-in multi-endpoint / failover / multi-domain LDAP configuration for a single tenant.
Recommended approach (customization) is to replace/extend the LDAP authentication flow to try multiple endpoints in order:
In ABP, this kind of change is typically done by replacing the related service via DI (using [Dependency(ReplaceServices = true)] + [ExposeServices(...)]) rather than modifying framework/module code.
About “extend LdapManager”: conceptually yes (iterate through multiple configurations), but the robust way in ABP is to replace the concrete LDAP authentication/bind service that the Account LDAP provider uses, so the rest of the Account module remains unchanged.
Plans/future versions: I couldn’t verify any official roadmap or issue stating that multi-endpoint-per-tenant LDAP is planned. No verified source available for that part.
Links I can verify as accessible right now:
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
https://eform/HRBack/api/abp/application-configuration returns 200, but your browser console still shows requests going to /api/abp/application-configuration (without /HRBack), the only remaining fix is to force abp.appPath for Swagger UI (and ensure the Swagger endpoint also includes /HRBack). Otherwise abp.swagger.js will keep calling the root.Use this exact configuration in *.HttpApi.Host OnApplicationInitialization (note the real <script> tag and the /HRBack/ trailing slash):
app.UseSwagger();
app.UseAbpSwaggerUI(options =>
{
// Important: Use the correct Swagger JSON URL under the virtual directory
options.SwaggerEndpoint("/HRBack/swagger/v1/swagger.json", "My API");
// Important: Force ABP base path for swagger UI script (abp.swagger.js)
var builder = new StringBuilder(options.HeadContent);
builder.AppendLine("<script>abp.appPath = '/HRBack/';</script>");
options.HeadContent = builder.ToString();
var configuration = context.GetConfiguration();
options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
});
After republish, hard refresh the swagger page (Ctrl+F5) and confirm in DevTools → Network:
GET https://eform/HRBack/api/abp/application-configuration is 200https://eform/api/abp/application-configurationThis is the same root cause you saw earlier: Swagger UI works, swagger.json is OK, but abp.swagger.js uses abp.appPath and defaults to / unless you force it under an IIS virtual directory.
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.