Hi, just to know if there is any feedback about this issue
The default tiered Blazor Server project template includes options.IntrospectAccessToken() in the cookie authentication configuration. This causes an HTTP POST to the AuthServer's /connect/introspect endpoint for every HTTP request — including static assets (CSS, JS, images), SignalR negotiation, and _blazor calls.
When UseDynamicClaims() + IsDynamicClaimsEnabled = true are also configured (which they are by default in the same template), session validation is already handled via Redis/DB by IdentitySessionDynamicClaimsPrincipalContributor. The introspection is completely redundant.
The result is ~50 unnecessary HTTP POST calls to the AuthServer after login, adding 5–8 seconds of latency to the first page load.
// Generated by ABP template in BlazorModule.cs (tiered Blazor Server)
context.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(365);
options.IntrospectAccessToken(); // ❌ Causes HTTP POST per request
})
.AddAbpOpenIdConnect("oidc", options => { ... });
// Also in the same template:
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.IsDynamicClaimsEnabled = true; // ✅ Already validates sessions
});
// In OnApplicationInitialization:
app.UseDynamicClaims(); // ✅ Already validates sessions via Redis/DB
With IntrospectAccessToken() enabled, after the user authenticates:
[Browser] GET /css/styles.css
→ [Blazor Server] POST /connect/introspect → [AuthServer] ~50-200ms
← 200 OK (token is active)
← serves CSS
[Browser] GET /js/app.js
→ [Blazor Server] POST /connect/introspect → [AuthServer] ~50-200ms
← 200 OK (token is active)
← serves JS
[Browser] POST /_blazor/negotiate
→ [Blazor Server] POST /connect/introspect → [AuthServer] ~50-200ms
...
(repeated ~50 times for all resources loaded during page initialization)
The default template configures both mechanisms, but only one is needed:
| Mechanism | Cost per request | Session revocation | Source |
|-----------|-----------------|-------------------|--------|
| IntrospectAccessToken() | HTTP POST to AuthServer (50–200ms) | Yes | Cookie middleware |
| UseDynamicClaims() + IsDynamicClaimsEnabled | Redis/DB lookup (<1ms) | Yes | IdentitySessionDynamicClaimsPrincipalContributor |
IdentitySessionDynamicClaimsPrincipalContributor (part of UseDynamicClaims()) validates the session on every request by checking the session ID in Redis/distributed cache. If an admin revokes a session (e.g., via Identity Management → Users → Sessions), the user is logged out on the next request — exactly the same behavior as IntrospectAccessToken(), but using a local Redis lookup instead of an HTTP round-trip to the AuthServer.
Tested on a Blazor Server tiered application (ABP 10.0.0 Commercial, .NET 10, localhost).
| Metric | With IntrospectAccessToken | Without IntrospectAccessToken | Improvement | |--------|---------------------------|-------------------------------|-------------| | Introspection calls | ~50 POST requests | 0 | -100% | | AuthServer load | ~50 requests/page load | 0 | -100% | | Post-login page load time | ~8 seconds | ~2 seconds | ~6 seconds faster |
IntrospectAccessToken() adds an HTTP call to each one._blazor/negotiate, SignalR WebSocket upgrade, and multiple _blazor framework calls — easily 50+ requests.Remove IntrospectAccessToken() from the default Blazor Server tiered template, since UseDynamicClaims() + IsDynamicClaimsEnabled = true already provide session validation:
.AddCookie("Cookies", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(365);
// IntrospectAccessToken() removed — session validation is handled by
// UseDynamicClaims() + IsDynamicClaimsEnabled via IdentitySessionDynamicClaimsPrincipalContributor
})
If IntrospectAccessToken() is intentionally kept for scenarios where UseDynamicClaims() is not enabled, consider:
IntrospectAccessToken() only when IsDynamicClaimsEnabled is falseSession revocation still works: IdentitySessionDynamicClaimsPrincipalContributor checks session validity in Redis/DB on every request. Revoked sessions are detected immediately.
Token validation still works: The HttpApi.Host validates JWT tokens locally with AddAbpJwtBearer(). The Blazor Server app sends the access token in API calls, and the API host validates it without needing introspection.
The AuthServer uses UseLocalServer(): It validates its own tokens locally, not via introspection.
No behavioral change for users: Login, logout, session timeout, and session revocation all work identically. The only difference is the elimination of redundant HTTP calls.
After removing IntrospectAccessToken():
/connect/introspect requests from BlazorUseDynamicClaims() handles it)Remove the IntrospectAccessToken() call from the Blazor module:
.AddCookie("Cookies", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(365);
// options.IntrospectAccessToken(); // Remove this line
})
Ensure UseDynamicClaims() and IsDynamicClaimsEnabled = true remain configured (they are by default).
MvcCachedApplicationConfigurationClient.GetRemoteConfigurationAsync() fetches application-configuration and application-localization sequentially, but they can safely run in parallel. The localization call depends on a culture name that is already available from CultureInfo.CurrentCulture (set by ABP's RequestLocalizationMiddleware) — it does not need to wait for the configuration response.
This is a low-risk, high-impact optimization for all tiered Blazor Server deployments.
[Request starts]
→ GET /api/abp/application-configuration ~120ms
← Response received
→ GET /api/abp/application-localization ~200ms (waits for config to finish)
← Response received
[Total: ~320ms]
The sequential dependency exists because the current code extracts cultureName from config.Localization.CurrentCulture.Name before calling the localization endpoint:
// Current implementation in MvcCachedApplicationConfigurationClient
private async Task<ApplicationConfigurationDto> GetRemoteConfigurationAsync()
{
var config = await ApplicationConfigurationAppService.GetAsync(
new ApplicationConfigurationRequestOptions { IncludeLocalizationResources = false }
);
// ❌ Waits for config just to get cultureName
config.Localization.Resources = (await ApplicationLocalizationClientProxy.GetAsync(
new ApplicationLocalizationRequestDto
{
CultureName = config.Localization.CurrentCulture.Name,
OnlyDynamics = true
}
)).Resources;
return config;
}
[Request starts]
→ GET /api/abp/application-configuration ~120ms (parallel)
→ GET /api/abp/application-localization ~200ms (parallel)
← Both responses received
[Total: ~200ms — savings: ~120ms per cache miss]
The key insight is that CultureInfo.CurrentCulture.Name is already set by ABP's RequestLocalizationMiddleware before MvcCachedApplicationConfigurationClient is invoked. It will always match config.Localization.CurrentCulture.Name, so we can use it directly without waiting for the configuration response:
// Proposed implementation
private async Task<ApplicationConfigurationDto> GetRemoteConfigurationAsync()
{
// CultureInfo.CurrentCulture is already set by ABP's RequestLocalizationMiddleware
var cultureName = CultureInfo.CurrentCulture.Name;
var configTask = ApplicationConfigurationAppService.GetAsync(
new ApplicationConfigurationRequestOptions { IncludeLocalizationResources = false }
);
var localizationTask = ApplicationLocalizationClientProxy.GetAsync(
new ApplicationLocalizationRequestDto
{
CultureName = cultureName,
OnlyDynamics = true
}
);
await Task.WhenAll(configTask, localizationTask);
var config = await configTask;
config.Localization.Resources = (await localizationTask).Resources;
return config;
}
Tested on a Blazor Server tiered application (ABP 10.0.0 Commercial, .NET 10, localhost).
| Metric | Sequential (before) | Parallel (after) | Savings |
|--------|---------------------|-------------------|---------|
| application-configuration | 124ms | 117ms | — |
| application-localization | 208ms | 237ms | — |
| Total wall time | 332ms (sum) | 238ms (max) | ~94ms |
| Metric | Sequential (before) | Parallel (after) | Savings |
|--------|---------------------|-------------------|---------|
| application-configuration | 95ms | 116ms | — |
| application-localization | 73ms | 120ms | — |
| Total wall time | 168ms (sum) | 121ms (max) | ~47ms |
Note: In production environments with higher latency to the API host, the savings scale proportionally. If the shorter call takes 200ms in production, you save ~200ms per cache miss.
Each page load triggers this on cache miss during both prerender and interactive phases. Savings compound:
CultureInfo.CurrentCulture is guaranteed correct: ABP's RequestLocalizationMiddleware runs early in the pipeline and sets the culture before any application code executes. The MvcCachedApplicationConfigurationClient is invoked later (during Razor page rendering or Blazor circuit initialization), so the culture is already set.
No behavioral change: The localization endpoint receives the exact same CultureName — we're just reading it from a different (but equivalent) source.
No API contract change: Both endpoints are called with the same parameters as before.
Cache key unchanged: The distributed cache key generation (MvcCachedApplicationConfigurationClientHelper.CreateCacheKey) is not affected.
MvcCachedApplicationConfigurationClient)Until this is addressed in ABP, applications can replace the service by implementing ICachedApplicationConfigurationClient directly with [Dependency(ReplaceServices = true)]:
[ExposeServices(typeof(ICachedApplicationConfigurationClient))]
[Dependency(ReplaceServices = true)]
public class ParallelCachedApplicationConfigurationClient
: ICachedApplicationConfigurationClient, ITransientDependency
{
// ... (full implementation available on request)
}
Important: The module containing this replacement must declare [DependsOn(typeof(AbpAspNetCoreMvcClientModule))] to ensure it loads after ABP's default registration.
Hi, it's the second time the bot closed this issue. Could someone from ABP check it, please?
This ABP Suite issue is also reported as a request in https://abp.io/qa/questions/8803/3a18c029-e901-3c3a-86d9-0bc6255b445f. But I am reporting it as issue because I think it is important.
When creating a master - child entities, ABP suite is generating managers (DomainService) for master (AggregateRoot) and child (Entity) entities. This means child entities can be managed directly from child services (application and domain services) without using the master entity, and I think it is not DDD friendly. My suggestions for ABP Suite is:
Hi Engican, do you need this question to be opened or is it OK to close it?
[EngincanV] said:
[nacho] said: Hi! I just reopened what the bot closed :)
Just a reminder of something that I realized and wrote in my fist comment: "I do not see the option to get the solution configuration with ABP Studio, the button is not visible now"
I created a new solution with ABP Studio and the option "Solution Configuration" is available
Hi, this feature was added with v0.7.6+, and it basically puts configuration into your
*.abpslnfile while creating an application (section name:creatingStudioConfiguration). So, probably your solution was created before v0.7.6 and this is the reason why you don't see the "Solution Configuration" context menu item.
OK! this is our abpsln file, in case it helps:
{
"id": "fff39d08-98bc-4ab2-9e28-c05a141732a2",
"template": "empty",
"modules": {
"Driven2u.Cpaas": {
"path": "Driven2u.Cpaas.abpmdl"
}
},
"runProfiles": {
"Default": {
"path": "etc/abp-studio/run-profiles/Default.abprun.json"
}
}
}
Hi! I just reopened what the bot closed :)
Just a reminder of something that I realized and wrote in my fist comment: "I do not see the option to get the solution configuration with ABP Studio, the button is not visible now"
I created a new solution with ABP Studio and the option "Solution Configuration" is available

Hi, this is not a bug, but it something strange (in my option) that could cause confusion to the final users...
In "Adminsitration > Settings > Account > Idle Session Timeout" there is a link to ABP documentation called "Learn more about idle session timeout" (See screenshot below). I think there should not be any link to ABP docs or website (at least in ABP commercial).

[EngincanV] said:
[nacho] said: Thanks, bot! 🙂ABP team, I reported this so you can take a look, as it could become a time-consuming issue if all three developers are experiencing the same problem.I hope the information provided is enough to investigate. If not, I’d be happy to show you the issue live by sharing my screen, just let me know.
Hi, sorry for the bot's response, it sometimes helps :)
Thanks for the detailed information and sharing the
appsettings.jsonfile with the error logs, this helps a lot to identify the problem.[ERR] Error getting value from 'MigrationsMigrationsDbContext' on 'Volo.Abp.Suite.Models.Solution'.
Before you update the
appsettings.jsonfile, do you remember the value of theMigrationsMigrationsDbContextfield? Also, I will create a tiered solution and try to understand the problem better, but by any chance, can you also share the not-updated (default) values of theappsettings.jsonfile, so I can see the changes and check at the code level? (I'll try to identify at which point ABP Suite is currently unable to determine the related path)Regards.
No, sorry... we don't have the previous appsettings.json. When we encountered the issue, we removed the solution from ABP Suite and tried to add it again (also without success).
What we can share with you (preferably in an online meeting) are the error messages and logs we get when trying to add or open the solution in ABP Suite (after removing the solution from ABP Suite). That might be a quicker way for you to troubleshoot the issue.