Open Closed

[Performance] Default Blazor Server template includes IntrospectAccessToken() which is redundant with UseDynamicClaims() #10405


User avatar
0
nacho created

[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

  1. No static file exclusion: The cookie authentication middleware runs for ALL requests, including static files. IntrospectAccessToken() adds an HTTP call to each one.
  2. Blazor Server is request-heavy: A single page load involves CSS, JS, images, fonts, _blazor/negotiate, SignalR WebSocket upgrade, and multiple _blazor framework calls — easily 50+ requests.
  3. Serial bottleneck: Many of these requests happen concurrently, creating a burst of ~50 simultaneous introspection calls against the AuthServer.
  4. 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(&quot;Cookies&quot;, options =&gt;
{
    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:

  1. Documenting the interaction between the two mechanisms so users understand they're redundant
  2. Conditionally including IntrospectAccessToken() only when IsDynamicClaimsEnabled is false
  3. At minimum: excluding static files from introspection (e.g., by path prefix)

Why removing it is safe

  1. Session revocation still works: IdentitySessionDynamicClaimsPrincipalContributor checks session validity in Redis/DB on every request. Revoked sessions are detected immediately.

  2. 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.

  3. The AuthServer uses UseLocalServer(): It validates its own tokens locally, not via introspection.

  4. 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():

  1. Login → verify access to protected pages works normally
  2. Check AuthServer logs → confirm zero /connect/introspect requests from Blazor
  3. Revoke a session via Identity Management → Users → Sessions → verify the user is logged out on next request (proves UseDynamicClaims() handles it)
  4. Measure post-login page load time → confirm ~5-8 second improvement
  5. 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)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • In a tiered Blazor Server (or MVC) UI, enabling options.IntrospectAccessToken() on the cookie scheme will indeed trigger a remote /connect/introspect call 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, _blazor endpoints, 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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Thanks, I will check this case. You can remove this method call in your app.

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.2.0-preview. Updated on February 05, 2026, 13:24
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.