Activities of "maliming"

Hi,

The telemetry exit handlers keep disposed applications alive even after the telemetry task finishes. We've prepared a fix.

Please keep Abp:Telemetry:IsEnabled set to false in your test configuration for now.

Thanks

Hi,

When two navigation properties point to the same entity (IdentityUser here), ABP Suite generates the lookup only for the first one in UserMaintenances.razor.cs. The razor page still uses supervisorCollection, so the build fails.

For now, you can add the missing part to UserMaintenances.Extended.razor.cs. Suite doesn't overwrite this file when you regenerate the page.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AbpSolution1.Shared;

namespace AbpSolution1.Blazor.Pages;

public partial class UserMaintenances
{
    private IReadOnlyList<LookupDto<Guid>> supervisorCollection { get; set; } = new List<LookupDto<Guid>>();

    protected override async Task OnParametersSetAsync()
    {
        supervisorCollection = (await UserMaintenancesAppService.GetsupervisorLookupAsync(new LookupRequestDto())).Items;
    }
}

The app service already has GetsupervisorLookupAsync, so nothing else needs to change.

This is fixed in ABP Suite 10.7.0. After you upgrade Suite and regenerate the page, remove this code from the extended file, because the generated file will have supervisorCollection itself.

Thanks

Hi,

Support questions are the best place to share suggestions like this. We check them first, and we refund the ticket for suggestions, so you don't need to worry about your ticket count.

Thanks

Great 👍

Hi,

The Account module has a setting for this. Go to Administration > Settings > Account > General and enable Prevent email enumeration.

After that, the Forgot Password page shows the same message for both registered and unknown emails:

If the provided e-mail address is registered in the system, we will send a password reset link. If you don't get an email within a few minutes, please check your spam box or try again later.

The /api/account/send-password-reset-code endpoint also returns the same response in both cases.

If you use multi-tenancy, this setting is saved per tenant. Enabling it on the host doesn't change the tenants, so each tenant admin needs to enable it on their own settings page. To enable it for the host and all tenants at once, add this to the appsettings.json of your AuthServer (or the web app if it's not tiered):

"Settings": {
  "Abp.Account.PreventEmailEnumeration": "true"
}

If you want to use your own text, change the PreventEmailEnumerationPasswordResetMailSentMessage text of the AbpAccount resource in Administration > Language Management > Language Texts.

Thanks

Hi,

The dropdowns in CountryHdrs.razor use SAPServerCollection and NonSAPSystemCollection, but Suite declares them in CountryHdrs.razor.cs with the plural names (SAPServersCollection, NonSAPSystemsCollection).

You can add two properties to the CountryHdrs partial class in CountryHdrs.Extended.razor.cs. Keep the namespace and any code that's already in that file. Suite doesn't overwrite this file when you regenerate the entity:

using System;
using System.Collections.Generic;
using AbpSolution1.Shared;

namespace AbpSolution1.Blazor.Pages;

public partial class CountryHdrs
{
    private IReadOnlyList<LookupDto<Guid>> SAPServerCollection => SAPServersCollection;
    private IReadOnlyList<LookupDto<Guid>> NonSAPSystemCollection => NonSAPSystemsCollection;
}

This is fixed in ABP Suite 10.7.0. If you upgrade later and regenerate the entity, remove these two properties, otherwise the build will fail again. Your ticket has been refunded.

Thanks

Hi,

  1. ABP doesn't have a BFF setup for Angular at the moment. The "BFF" in the microservice docs means one API gateway per client type, it doesn't keep the tokens on the server.

  2. It's a reasonable request, and we'll create an internal task to look into it. It's a fairly big change, so it's hard to give you a timeline in the short term.

  3. @abp/ng.core only defines the auth services, and provideAbpOAuth() from @abp/ng.oauth registers the implementations. For cookie auth, don't call provideAbpOAuth() and provide your own implementations for these tokens from @abp/ng.core:

  • AuthService: init, isAuthenticated, navigateToLogin and logout, which can redirect to the login and logout endpoints of your BFF.
  • authGuard and asyncAuthGuard (and AuthGuard if you still use the class guard). They can check currentUser.isAuthenticated from ConfigStateService.
  • ApiInterceptor, also registered as HTTP_INTERCEPTORS. Keep the Accept-Language, tenant and X-Requested-With headers and don't add the Authorization header. With X-Requested-With, the cookie handler returns 401 instead of redirecting to the login page.
  • PIPE_TO_LOGIN_FN_KEY, CHECK_AUTHENTICATION_STATE_FN_KEY and AuthErrorFilterService.

The 401 handler in @abp/ng.theme.shared refreshes the application configuration and calls AuthService.logout(), so it will use your implementation. /api/abp/application-configuration works as long as the BFF forwards it to the gateway with the access token. Since the browser then signs in with a cookie, the BFF also needs CSRF protection.

Login, the guards, the 401 handling and logout work this way when the Angular app is served from the same origin as the host that issues the cookie. We don't have a full BFF sample yet, so please take this as a starting point.

Thanks

Hi,

  1. You can keep the refresh_token in memory too. It is then shared between tabs through the same SharedWorker as the access token. Extend MemoryTokenStorageService like this:
import { Injectable, inject } from '@angular/core';
import { AbpLocalStorageService } from '@abp/ng.core';
import { MemoryTokenStorageService } from '@abp/ng.oauth';

@Injectable({ providedIn: 'root' })
export class RefreshTokenInMemoryStorageService extends MemoryTokenStorageService {
  constructor() {
    super();
    const storage = this as any;
    storage.keysShouldStoreInMemory.push('refresh_token');
    storage.port?.postMessage({ action: 'get', key: 'refresh_token' });
    inject(AbpLocalStorageService).removeItem('refresh_token');
  }
}

Then register it after provideAbpOAuth() in app.config.ts:

import { MemoryTokenStorageService, provideAbpOAuth } from '@abp/ng.oauth';
import { RefreshTokenInMemoryStorageService } from './refresh-token-in-memory-storage.service';

providers: [
  // ...
  provideAbpOAuth(),
  { provide: MemoryTokenStorageService, useExisting: RefreshTokenInMemoryStorageService },
  // ...
]

A few notes:

  • This is for a normal (non-SSR) Angular app.
  • If you register OAuthStorage yourself (for example with localStorage), remove that. Otherwise this class is not used.
  • The SharedWorker is created from a data: URL, so your CSP needs to allow data: in worker-src. If the worker can't start, each tab keeps its own tokens.
  • This class uses private members of MemoryTokenStorageService, so treat it as a temporary workaround.
  • The removeItem call deletes the refresh token that older builds left in localStorage.
  • The tokens are not kept after the last tab is closed, and may be lost when the only open tab is reloaded. The user then needs to click Login again. As long as the AuthServer cookie is still valid, they won't be asked for the password.
  1. Each tab runs its own refresh timer, so two tabs can send the same refresh token. If the token was already redeemed when the second request arrives, that request is rejected as a replay. We don't have a workaround that keeps the zero leeway for now. If you can accept a short reuse window, change your current zero leeway to 10 seconds in the PreConfigureServices of your AuthServer module:
PreConfigure<OpenIddictServerBuilder>(builder =>
{
    builder.SetRefreshTokenReuseLeeway(TimeSpan.FromSeconds(10));
});

A redeemed refresh token is then accepted again only within 10 seconds of its first redemption.

  1. We'll pass the rest to the Angular team: a supported option to keep the refresh token in memory, letting only one tab refresh at a time, the HttpOnly cookie / BFF option, and the docs for the token storage and the worker-src CSP.

Thanks

Hi,

The Angular logout already calls the revocation endpoint, and the AuthServer deletes the user's session. The APIs then rely on dynamic claims to notice that the session is gone. In the microservice solution this check has two problems.

  1. In each API service, app.UseAuthorization() runs before app.UseDynamicClaims(). A controller that relies only on [Authorize] has already passed authorization when dynamic claims clears the user, so it still returns data. Move the existing app.UseAuthorization() call to right after the existing app.UseDynamicClaims() call, and keep all the other middleware calls where they are:
app.UseDynamicClaims();
app.UseAuthorization();

After this change, such a controller rejects the revoked token with 403, not 401.

  1. The API services other than the identity service cache the dynamic claims per user, not per session. Once the same user sends a request with another session (another browser, or after logging in again), that cache is filled again and the revoked token is accepted again as long as it hasn't expired. You can use this workaround for now: when a session is deleted, put its id into the distributed cache, and reject any token that carries that session id.

Add this handler to the AuthServer and the IdentityService (the apps that delete sessions):

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;

namespace MyCompanyName.MyProjectName;

public class RevokedIdentitySessionHandler : ILocalEventHandler<EntityDeletedEventData<IdentitySession>>, ITransientDependency
{
    protected IDistributedCache<RevokedIdentitySessionCacheItem> Cache { get; }

    public RevokedIdentitySessionHandler(IDistributedCache<RevokedIdentitySessionCacheItem> cache)
    {
        Cache = cache;
    }

    public virtual async Task HandleEventAsync(EntityDeletedEventData<IdentitySession> eventData)
    {
        // Keep it longer than your maximum access token lifetime plus the clock skew.
        await Cache.SetAsync(eventData.Entity.SessionId, new RevokedIdentitySessionCacheItem(), new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
        }, hideErrors: false);
    }
}

[Serializable]
[IgnoreMultiTenancy]
[CacheName("RevokedIdentitySession")]
public class RevokedIdentitySessionCacheItem
{
}

Add this contributor to each of your other API services, with the same RevokedIdentitySessionCacheItem class:

using System;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.Caching;
using Volo.Abp.Security.Claims;

namespace MyCompanyName.MyProjectName;

public class RevokedIdentitySessionDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase
{
    public override async Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
    {
        var sessionId = context.ClaimsPrincipal.Identities.FirstOrDefault()?.FindSessionId();
        if (sessionId == null)
        {
            return;
        }

        var cache = context.GetRequiredService<IDistributedCache<RevokedIdentitySessionCacheItem>>();
        try
        {
            if (await cache.GetAsync(sessionId, hideErrors: false) != null)
            {
                context.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity());
            }
        }
        catch (Exception e)
        {
            context.GetRequiredService<ILogger<RevokedIdentitySessionDynamicClaimsPrincipalContributor>>()
                .LogError(e, "Could not check the revoked session cache.");
            context.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity());
        }
    }
}

This needs dynamic claims enabled in these services (IsDynamicClaimsEnabled = true, which the template sets), and all apps need to use the same Redis database and the same AbpDistributedCacheOptions.KeyPrefix. It only covers sessions deleted after you deploy it. You don't need reference tokens or introspection for this workaround.

We'll fix both in the framework and the microservice template.

Thanks

Hi,

Thanks for the suggestion. The Chat module doesn't have this today, and the avatar is rendered inside the Chat page, so there is no smaller extension point to override right now.

We'll analyze this request: show the profile picture when the user has one, keep the initials as the fallback, and make it optional.

Thanks

Showing 21 to 30 of 12910 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 30, 2026, 06:34
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.