[Performance] Default Blazor Server template includes IntrospectAccessToken() which is redundant with UseDynamicClaims() — causes ~50 extra HTTP calls per page load
Summary
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.
Current behavior (default template)
// 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
What happens on every request
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)
Two redundant session validation mechanisms
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.
Measured impact
Tested on a Blazor Server tiered application (ABP 10.0.0 Commercial, .NET 10, localhost).
Post-login page load
| 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 |
Why the impact is so large
- No static file exclusion: The cookie authentication middleware runs for ALL requests, including static files.
IntrospectAccessToken()adds an HTTP call to each one. - Blazor Server is request-heavy: A single page load involves CSS, JS, images, fonts,
_blazor/negotiate, SignalR WebSocket upgrade, and multiple_blazorframework calls — easily 50+ requests. - Serial bottleneck: Many of these requests happen concurrently, creating a burst of ~50 simultaneous introspection calls against the AuthServer.
- Latency compounds: Even at 50ms per introspection on localhost, 50 calls × 50ms = 2.5 seconds of added AuthServer processing time, causing request queuing and slower responses.
Proposed fix
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:
- Documenting the interaction between the two mechanisms so users understand they're redundant
- Conditionally including
IntrospectAccessToken()only whenIsDynamicClaimsEnabledisfalse - At minimum: excluding static files from introspection (e.g., by path prefix)
Why removing it is safe
Session revocation still works:
IdentitySessionDynamicClaimsPrincipalContributorchecks 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.
Verification steps
After removing IntrospectAccessToken():
- Login → verify access to protected pages works normally
- Check AuthServer logs → confirm zero
/connect/introspectrequests from Blazor - Revoke a session via Identity Management → Users → Sessions → verify the user is logged out on next request (proves
UseDynamicClaims()handles it) - Measure post-login page load time → confirm ~5-8 second improvement
- Let the cookie expire → verify re-authentication is triggered normally
Affected scenarios
- Tiered Blazor Server — severely affected (50+ introspection calls per page load)
- Tiered MVC — also affected (fewer requests per page load, but still impacted)
- Non-tiered / monolith — not affected (no remote introspection endpoint)
Workaround
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).
Environment
- ABP Framework version: 10.0.0
- ABP Commercial version: 10.0.0
- .NET version: 10.0
- Hosting: Blazor Server (tiered architecture)
- Template: Default ABP Commercial Blazor Server (tiered)
2 Answer(s)
-
0
- In a tiered Blazor Server (or MVC) UI, enabling
options.IntrospectAccessToken()on the cookie scheme will indeed trigger a remote/connect/introspectcall during cookie validation on every request that hits the authentication middleware. In Blazor Server that can be dozens of requests during initial load (static files,_blazorendpoints, negotiate, etc.), so your observation about the performance impact is valid. - If your solution already uses ABP Dynamic Claims (
options.IsDynamicClaimsEnabled = true+app.UseDynamicClaims()), then you already have a per-request “is session still valid?” style check (via ABP’s dynamic-claims pipeline + Identity session integration). Keeping token introspection on top typically becomes redundant and expensive in this setup.
Recommended action (what to change in your app): remove introspection from the UI cookie scheme
.AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); // Remove this to avoid /connect/introspect on every request: // options.IntrospectAccessToken(); })Why this is safe in the default ABP setup you described
- With Dynamic Claims enabled, ABP updates/overrides claims on each request using the latest state, and (when Identity session integration is used) it can reflect session revocation without requiring a remote introspection roundtrip.
- Your APIs (HttpApi.Host) typically validate JWT access tokens locally (JWT bearer validation), so they don’t need introspection either.
If you still need introspection for a specific security requirement, don’t apply it globally to the cookie scheme in a request-heavy UI; apply it only where needed (otherwise it will run for static assets too).
Link checked:
- https://abp.io/docs/latest/framework/fundamentals/dynamic-claims
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 a tiered Blazor Server (or MVC) UI, enabling
-
0
Thanks, I will check this case. You can remove this method call in your app.