Activities of "AI-Bot"

  • Yes, this is expected by default when you use self-contained JWT access tokens with ABP (OpenIddict) and your APIs validate them locally (signature + exp, etc.).
    Logging out from the Angular app typically:

    1. ends the local session in the browser (clears tokens/storage), and/or
    2. calls the OpenID Connect end session endpoint
      but it does not automatically invalidate already-issued JWT access tokens. A copied JWT will keep working until it expires unless you add server-side revocation/introspection checks.
  • Recommended way to make logout invalidate access immediately: switch to a model where the API can detect revocation server-side, e.g. token introspection / reference tokens (or otherwise force the API to consult a server-side store on each request).

    • In ABP, one supported approach is to introspect access tokens, so the API doesn’t just accept any still-unexpired JWT purely by signature/exp. This is referenced in an ABP support answer where enabling introspection solved token/session mismatch behavior:
      • enable something like options.IntrospectAccessToken(); in the API authentication configuration (exact placement depends on your host/API project setup).
        This changes the validation behavior from “pure JWT validation” toward “ask the auth server / validate token entry”, enabling immediate invalidation when the token is revoked/removed.
  • About your considered options:

    1. Reference access tokens + validation/introspection enabled: This is the standard way to get immediate revocation semantics (API can reject a token right after logout/revocation). This is typically the most robust option for your requirement.
    2. Calling the revocation endpoint on logout from Angular: This can help, but only if your APIs are doing revocation-aware validation (introspection/token store check). If the API continues to accept self-contained JWTs purely locally, revoking at the auth server won’t matter to the API.
    3. Short-lived access tokens (what you already have: 15 min): reduces exposure window but doesn’t give “immediate logout invalidation”.
  • Practical guidance (secure-by-design):

    • If you need “logout means token is unusable immediately”, you must ensure the API validates tokens with a server-side check (introspection/reference tokens), not just offline JWT validation.
    • If you also use refresh tokens, logout should revoke/clear them too (so the client can’t silently obtain a new access token).

Sources (validated)

  • https://abp.io/support/questions/5071

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.

  • In ABP v10.7.0, the Chat module UI does not have a built-in integration point/setting to automatically use Account Pro profile pictures for avatars (contact list + conversation header). With the publicly verifiable material available here, there is no documented configuration switch or extension point that enables this behavior without overriding/replacing UI components/pages.

  • The good news is: Account Pro already exposes an HTTP API endpoint to retrieve a user’s profile picture, so technically the Chat UI can be adjusted to call that endpoint and fall back to initials when there is no picture:

    • GET /api/account/profile-picture-file/{userId} (returns IRemoteStreamContent)
  • Therefore, the practical options today are:

    1. UI override/customization (recommended path with current capabilities): override the Chat module’s Blazor components that render avatars and update them to:

      • try rendering an <img> using the Account profile-picture endpoint URL for that user
      • if it fails/empty, render the initials (current behavior)

      This still involves overriding module UI pieces, but it can be done at the component level (not necessarily “replace the entire Chat page”), depending on how Chat is structured in your solution’s source.

    2. Get Chat module source and implement it in the module itself (then you’ll need to maintain the delta across updates):

      • abp get-source Volo.Chat
  • If you want this supported directly by the module (so you don’t maintain UI overrides), that’s currently a product feature request for the commercial Chat module (i.e., the module would need to ship avatar support + a toggle, as you suggested).

(Links are validated below.)

  • https://abp.io/docs/10.7/modules/chat
  • https://abp.io/support/questions/7074

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.

  • In Blazor Web App / Server prerendered (InteractiveAuto / first full load), the page can render before the browser-side “system theme (prefers-color-scheme)” detection runs.
    In that short window, LeptonX may already look dark (because it is mostly CSS-variable driven), while MudBlazor still uses its default light palette, so grids/lists/content surfaces appear white until the client finishes initialization.

  • Fix: bind MudBlazor’s dark mode to LeptonX’s actual resolved style and re-render after the client applies it (so MudBlazor doesn’t stay with the initial light palette).

    1. Ensure your layout contains MudThemeProvider and you control IsDarkMode:
@* MainLayout.razor (or your app layout) *@
  @using MudBlazor

  <MudThemeProvider IsDarkMode="_isDarkMode" />
  <MudDialogProvider />
  <MudSnackbarProvider />

  @Body

  @code {
      private bool _isDarkMode;

      protected override async Task OnAfterRenderAsync(bool firstRender)
      {
          if (!firstRender)
              return;

          // Read the resolved theme from the DOM AFTER LeptonX JS applies "System" (light/dark)
          _isDarkMode = await JS.InvokeAsync<bool>("abpLeptonXMud.isDarkMode");
          StateHasChanged(); // forces MudBlazor to re-render with correct palette
      }

      [Inject] IJSRuntime JS { get; set; } = default!;
  }
  1. Add a tiny JS helper that returns the actual dark/light state from the DOM.
    Create /wwwroot/abpLeptonXMud.js:
window.abpLeptonXMud = {
    isDarkMode: function () {
      // LeptonX applies theme on the <html> element using Bootstrap's data attribute.
      // Dark mode => data-bs-theme="dark"
      const theme = document.documentElement.getAttribute('data-bs-theme');
      return theme === 'dark';
    }
  };
  1. Include this script in your global bundle (or directly in the host page) after ABP/LeptonX scripts are loaded.
    (In ABP, do it via bundling so it’s always present on first load.)

Once MudThemeProvider gets the correct IsDarkMode value after the first client render, MudBlazor components (MudDataGrid rows, MudList, surfaces, etc.) will switch to the dark palette consistently, including on first visit / full reload.


Links (verified):

  • https://abp.io/docs/10.7/ui-themes/lepton-x/blazor?UI=BlazorServer&BlazorUI=MudBlazor
  • https://abp.io/docs/latest/ui-themes/lepton-x/blazor

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.

  • With Shared User Accounts (TenantUserSharingStrategy.Shared), the supported/standard way to add a host-created (global) user into a tenant is the invitation flow. Joining a tenant is designed to happen by accepting an invitation (and roles can be pre-assigned at invitation time).
  • There is no documented/verified API/option in ABP 10.6 that “auto-accepts” invitations (i.e., adds the user to the tenant and assigns roles) without executing the accept flow.

To mass-associate users during migration (e.g., in SaasDataSeedContributor), the verified approach is:

  1. Create invitations programmatically for each tenant + user
  2. Assign roles on the invitation (so they get roles immediately after they join)
  3. If you don’t want users to deal with email, replace the email sender so invitations don’t require SMTP and you can distribute/handle the accept links internally (or log/store them)

This still uses the official invitation/accept mechanism, but removes the operational burden of SMTP.


Practical seeding pattern (SaasDataSeedContributor)

  • Enumerate tenants (host side)
  • For each tenant, create invitations for the target users (by email)
  • Include role assignment in the invitation request (so tenant roles are set when the user joins)

I’m not including exact service/class names for “create invitation” from code because I couldn’t verify them from source in the available repositories here. The official docs confirm the behavior, but don’t expose the internal API surface in the snippet.


Optional: Remove SMTP requirement (so migration doesn’t block on email)

Replace IIdentityEmailSender to not send and instead log/persist the invite link. This is a verified approach from an ABP support ticket (used when SMTP is blocked). You can then have your migration/admin process consume those links (or forward them via internal tooling).

If your goal is truly “no user interaction at all”, that would require calling the internal “accept invitation” logic directly (or manipulating invitation/tenant-user tables), but I don’t have a verified source for the required public API or the correct persistence model to recommend doing that safely.


(light) sources

  • https://abp.io/docs/10.6/modules/account/shared-user-accounts (validated)
  • https://abp.io/support/questions/10624 (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.

  • This happens because the distributed handler (AccountIdentityUserCreatedEventHandler) runs in the RabbitMQ consumer pipeline without an ambient ABP Unit of Work, but it calls Identity token generation (AbpSingleActiveTokenProvider.GenerateAsync(...)), which needs an EF Core DbContext. Without a UOW, ABP throws:

    A DbContext can only be created inside a unit of work!

  • Fix: ensure the handler method is executed inside a Unit of Work via interception:

    1. Add [UnitOfWork]
    2. Make the method virtual (required for ABP’s dynamic proxy interception)
    3. Add using Volo.Abp.Uow;

    Example:

using System.Threading.Tasks;
  using Volo.Abp.Uow;

  public class AccountIdentityUserCreatedEventHandler
  {
      [UnitOfWork]
      public virtual async Task HandleEventAsync(IdentityUserCreatedEto eventData)
      {
          // existing handler code (no other changes needed)
      }
  }
  • Why the virtual matters: ABP applies [UnitOfWork] through dynamic proxy interceptors. If the method isn’t virtual, the interceptor won’t run, so no UOW is created, and token generation fails. This matches the behavior you observed and also the known pattern from ABP support Q&A.

  • Alternative (works but usually not necessary if interception works): manually begin a UOW inside the handler:

using var uow = _unitOfWorkManager.Begin();
  // ...
  await uow.CompleteAsync();

But the recommended approach is the attribute + virtual so the infrastructure manages it consistently.

Links checked:

  • https://abp.io/docs/latest/framework/infrastructure/event-bus/distributed
  • https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work
  • https://abp.io/support/questions/3201

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.

  • In ABP Commercial Chat module, the Chat → Messaging → Searching permission is used to control whether a user is allowed to search / start a new conversation with arbitrary users (i.e., users they don’t already have a conversation/contact relationship with).

  • So the expected behavior for a user without this permission should be:

    1. They should not be able to start a brand-new conversation with a user they’ve never interacted with (or the UI should prevent selecting such users).
    2. If they still somehow reach a “new conversation” state (deep link, cached UI state, etc.), then when they try to send the first message, the server is expected to return 403 (Forbidden) (as you already see).
    3. The client should handle this 403 gracefully (show an authorization/toast/message and keep the page usable). The current behavior you report (unhandled AbpRemoteCallException: Forbidden that breaks the Blazor page) is a client-side unhandled exception and is not desirable.
  • Practically, you can treat what you’re seeing as a UI/client robustness bug in the Chat Blazor UI: the backend authorization is correctly enforced, but the Blazor page is not catching/handling AbpRemoteCallException for that action.

  • Workaround (recommended in your app until it’s fixed upstream):

    1. Hide/disable “start conversation / user search” UI if the user lacks the permission (via IAuthorizationService / IPermissionChecker / AuthorizeView).
    2. Add a try/catch around the SendMessageAsync call in the page/component and handle AbpRemoteCallException (status 403) by showing a message and reverting UI state instead of letting it bubble and kill the circuit.

    Example pattern (you’ll need to adapt names to your page code-behind):

try
  {
      await ConversationClientProxy.SendMessageAsync(input);
  }
  catch (Volo.Abp.Http.Client.AbpRemoteCallException ex)
  {
      // Forbidden
      if (ex.HttpStatusCode == System.Net.HttpStatusCode.Forbidden)
      {
          // Show a UI message/toast and prevent the page from breaking
          // e.g. await MessageService.Error(L["YouAreNotAllowedToStartConversation"]);
          return;
      }

      throw; // keep other errors visible
  }
  • Also, to prevent the “open conversation with someone never interacted with” path entirely: ensure the UI only allows initiating conversations from an allowed set (existing contacts/conversations) when Searching is not granted.

(Links omitted because I couldn’t retrieve repository sources via the available tools in this environment to validate exact implementation details/URLs.)


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.

  • This behavior is caused by authorization on the Chat settings application service being enforced at class level (requires Chat.SettingManagement). Since the Chat page initializes by calling SettingsAppService.GetAsync, the whole page fails to initialize for users who only have Chat.Messaging (Conversation → Messages) and they get redirected to Access Denied.

  • In ABP/Blazor, the most practical workaround (without modifying the commercial module source) is to override/replace the Chat page component and avoid calling the settings endpoint unless the user has the Setting Management permission.

Recommended fix (UI-side override)

  1. Override the Chat page/component from Volo.Chat.Blazor.MudBlazor (same technique used to override built-in module UI components).
  2. In your overridden OnInitializedAsync, check permission before calling SettingsAppService.GetAsync:
    • If user has Chat.SettingManagement: load settings (admin UI behavior)
    • Else: skip settings load and proceed with messaging UI using defaults

Example pattern (conceptual):

public override async Task OnInitializedAsync()
{
    // ... other init

    if (await AuthorizationService.IsGrantedAsync("Chat.SettingManagement"))
    {
        Settings = await SettingsAppService.GetAsync();
    }

    // continue loading conversations/messages
}

This keeps messaging access independent from settings management while preserving settings features for authorized users.

Alternative fix (service-side, if you own/extend backend)

If you have a customization point (e.g., you can replace the settings app service), provide a read-only settings endpoint that is authorized with Chat.Messaging (or no permission if settings are not sensitive), and make the UI call that endpoint instead. However, this requires backend customization/replacement and is typically more invasive than the UI override.


Sources

  • ABP Blazor UI overriding approach (used to replace module pages/components): https://docs.abp.io/en/abp/latest/UI/Blazor/Customization-Overriding-Components

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.

  • In ABP v10.7, the AI Management MudBlazor chat widget (ChatClientChat) ultimately relies on a MudBlazor text input for composing the message. In MudBlazor v9, AutoGrow is removed, and if the input is not explicitly configured to be multi-line, MudBlazor will render a single-line <input> (so Shift+Enter can’t insert a newline, because there is no <textarea>).

  • Fix: ensure the message composer is rendered as a textarea by configuring it as multi-line. In MudBlazor, the reliable way is to set Lines (or equivalent multi-line setting) on the underlying MudTextField used for the message input.

    • If you are using the ABP-provided ChatClientChat as-is and can’t change its internal markup, the only definitive fix is to override/replace the component (copy source into your project) and update the message field to be multi-line (i.e., Lines="X" so it becomes a <textarea>). After that, Shift+Enter will work normally (and Enter handling can still be kept for “send” logic).
  • Notes for MudBlazor v9 migration:

    • Don’t use AutoGrow anymore (it becomes a stray autogrow="" attribute, exactly as you observed).
    • InputSizing.Auto is about sizing behavior; it does not automatically convert a single-line input to a textarea. You still must configure the field as multi-line.
  • Minimal example of what the message editor should look like inside the chat component (the key part is Lines="3"; increase as needed):

<MudTextField @bind-Value="_message"
                Lines="3"
                Placeholder="Type a message..."
                Immediate="true" />

Sources:

  • https://abp.io/docs/10.7/modules/ai-management/
  • https://abp.io/docs/10.7/framework/ui/blazor/forms-validation?BlazorUI=MudBlazor

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.

  • This behavior comes from how LeptonX’s side menu keeps the currently active/selected item visible/expanded (so the user can always see “where they are”), even if you apply a client-side filter that hides non-matching items.
  • In the LeptonX MudBlazor side-menu, the menu content re-renders on navigation and the component logic re-applies the “active menu item” state. As a result, the previously active item can be injected back into the rendered list even though it doesn’t match your filter—while the rest of the menu stays filtered.

Fix (recommended): keep filtering and selection state in one place, and re-apply the filter after every navigation/change

  1. In your LeptonX side menu component where you keep the filter text, subscribe to NavigationManager.LocationChanged.
  2. Whenever navigation happens, re-apply the filter to the menu model/list after the menu state updates (call StateHasChanged).
  3. Ensure the filtered list is the only source used by the renderer (don’t render from the original full menu tree + “active item” separately).

Minimal pattern (put into your custom menu component/code-behind; adapt names to your project):

@inject NavigationManager NavigationManager

@code {
    private string _filterText = string.Empty;

    protected override void OnInitialized()
    {
        NavigationManager.LocationChanged += OnLocationChanged;
    }

    private void OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
    {
        // Re-apply filter after navigation so "active item" logic can't re-insert items
        ApplyFilter();
        InvokeAsync(StateHasChanged);
    }

    private void ApplyFilter()
    {
        // IMPORTANT:
        // Rebuild the rendered menu items *only* from your filter result.
        // Do not let rendering also pull in "active item" from the original full menu tree.
        //
        // Pseudocode:
        // FilteredMenu = FullMenu.FilterByDisplayName(_filterText);
    }

    public void Dispose()
    {
        NavigationManager.LocationChanged -= OnLocationChanged;
    }
}

Alternative behavior (if you want LeptonX’s “active item is always visible”):

  • Treat this as intended UX and change your expectation: LeptonX will always reveal the active item even if it doesn’t match the filter. In that case, consider clearing the filter automatically on navigation.

Links:

  • https://abp.io/docs/10.7/ui-themes/lepton-x/blazor?UI=Blazor&BlazorUI=MudBlazor
  • https://abp.io/docs/10.7/framework/ui/blazor/navigation-menu

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.

  • What you’re seeing is a real EF Core optimistic-concurrency race inside the OpenIddict token persistence flow (typically: two parallel operations touching the same token(s), e.g., refresh-token reuse/redeem + revoke-by-authorization). EF Core then throws DbUpdateConcurrencyException, and ABP wraps it as AbpDbConcurrencyException.
    The problematic part in your case is that the ABP exception is not being converted back into OpenIddict’s expected OpenIddictExceptions.ConcurrencyException, so it can bubble up and be logged as an application error (and sometimes surface as unhandled if it escapes the /connect/* pipeline).

  • There is a closely related, confirmed ABP issue in the /connect pipeline where the request UOW could complete after the response is flushed, causing timing/race problems around OpenIddict/Identity Session persistence; ABP fixed that by completing the request UOW before flushing /connect responses (included in later versions; PR below). While that specific ticket is about AbpSessions, it demonstrates ABP has been actively fixing /connect-pipeline timing/race behaviors, and your symptoms (fixed “minute marks”, steady background traffic, and concurrency exceptions in OpenIddict store) fit the same class of problems: background/automated clients + token rotation/revocation + concurrent writes.

1) Known issue / fix / workaround

Verified fix exists for /connect request timing (commit-before-flush) and is merged here:

  • https://github.com/abpframework/abp/pull/26017 (validated)

If you can, the most definitive mitigation is:

  1. Upgrade ABP to a version that includes that PR (post-10.5.0, per the linked support thread).
  2. After upgrade, remove any custom workaround you add for commit timing.

If you cannot upgrade immediately, the practical workaround pattern (already validated in ABP support) is to force the relevant OpenIddict work to be committed inside a new UnitOfWork earlier in the OpenIddict event pipeline (so the DB state is consistent/visible before other concurrent operations proceed). The support ticket shows this pattern for identity sessions; you can apply the same “commit now” idea to the part of the pipeline that creates/updates tokens in your scenario.

A second (often necessary) mitigation is at the client/API gateway level:

  • Ensure your clients never send parallel refresh-token requests using the same refresh token (serialize refresh calls per user/session), because refresh token rotation + parallel refresh is the canonical way to produce token-store concurrency conflicts.

2) Should AbpOpenIddictTokenStore.UpdateAsync/CreateAsync translate to OpenIddictExceptions.ConcurrencyException?

Yes: OpenIddict’s server pipeline expects concurrency failures from the store to be surfaced as OpenIddictExceptions.ConcurrencyException so it can translate them into protocol-appropriate responses (typically invalid_grant for refresh flows). If ABP lets AbpDbConcurrencyException escape, it will be logged as an infrastructure error and can leak as 500/unhandled depending on where it’s caught.

I couldn’t verify the exact AbpOpenIddictTokenStore implementation in source because the code-search tool backend was unavailable in this environment, so I can’t point to the exact catch/translate location in ABP 10.4.1. However, based on the observed behavior and OpenIddict conventions, this translation is the correct behavior and aligns with how OpenIddict stores are expected to report concurrency issues.

3) Could cleanup worker / refresh-token reuse explain hourly fixed pattern? Which options control them?

The fixed minute marks strongly suggest a scheduled/non-human client (mobile app token refresh loop, kiosk, integration service, health-check identity probe, etc.) rather than ABP’s own token pruning:

  • OpenIddict’s built-in pruning typically runs on an interval, but your timestamps repeating at very specific offsets each hour is more characteristic of one or more clients refreshing tokens on a schedule.
  • The SQL you captured (UPDATE ... WHERE AuthorizationId = @id) matches a “revoke tokens by authorization” style operation, which is commonly triggered by refresh token rotation/reuse detection or authorization revocation flows.

What to do operationally:

  1. Correlate the errors with client_id / subject / authorization_id involved at those times (enable higher OpenIddict/EF logging around failures).
  2. Identify the scheduled actor (job, integration, device fleet).
  3. Enforce single-flight refresh per subject/client (mutex in client; or server-side distributed lock keyed by (client_id, subject) for refresh endpoint).

Sources

  • https://abp.io/support/questions/10853 (validated)
  • https://github.com/abpframework/abp/pull/26017 (validated)
  • https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work (validated)
  • https://abp.io/docs/10.0/release-info/migration-guides/abp-10-0 (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.

Showing 1 to 10 of 1374 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 October 21, 2026, 11:09
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.