Open Closed

Intermittent `GrantedPolicies = 0` for authenticated user #10481


User avatar
0

ABP Support Request: Intermittent GrantedPolicies = 0 for authenticated admin user (ABP 10.0.2)

Hi ABP team,

We are investigating an intermittent production issue where an authenticated user suddenly loses effective permissions (menus/actions disappear) without logging out.

Environment

  • ABP version: 10.0.2
  • App type: ABP commercial-style modular app (Web + HttpApi.Host)
  • Cache stack: IDistributedCache (Redis) + custom in-memory L1 decorator over IDistributedCache
  • Redis: Azure Cache for Redis
  • Hosting: Azure App Service

Context

We already implemented an L1 cache decorator around IDistributedCache to handle the previously observed Redis transient/cancellation path.

That fix helped for Redis outages, but we are now seeing a different failure mode where cache is healthy, user is authenticated, yet GrantedPolicies becomes zero.

What we observed (latest incident)

Incident window (UTC): 2026-02-27 19:33:10Z to 19:35:53Z.

1) User is authenticated and claims look valid

  • Claims diagnostic: User=admin, UserId=e82af67f-ecf7-4559-8140-c9596aa9f91a, TotalClaims=11, Roles=[admin], AllKeyClaimsPresent=true

2) App config reports zero policies for that same authenticated user

Multiple requests in the same window show:

  • AppConfig diagnostic: User=admin has 0 grantedPolicies. Auth IsAuthenticated=true, CurrentUser set=true

Seen on:

  • /
  • /HostDashboard
  • /Abp/ApplicationConfigurationScript
  • /Abp/ApplicationLocalizationScript
  • /Abp/ServiceProxyScript

3) Authorization failures follow immediately

Many permission checks fail for the same request/user after the zero-policy state appears.

4) Redis appears healthy during this exact window

  • Redis status check ... IsConnected=true (periodic checks around the incident)
  • No corresponding Redis outage signature in this incident window

5) No classic application-configuration 500 chain this time

For this window we did not see the older pattern (API 500 + remote call exception/cancellation) that we saw in a separate earlier incident.

Why this is confusing

It looks like a valid, authenticated principal can occasionally resolve to an application configuration with GrantedPolicies = 0, and that value then appears to be reused by cache for a short period.

Questions for ABP team

  1. Are there known scenarios in ABP 10.0.2 where ApplicationConfiguration can be successfully generated with an empty GrantedPolicies dictionary for an authenticated user that otherwise has roles/claims?
  2. Is there any known race condition around dynamic claims/permission value providers that could yield transient empty results without exceptions?
  3. Is ABP expected to cache an empty GrantedPolicies result the same way as a normal one, and is there a recommended guard to avoid caching suspiciously empty results for authenticated users?
  4. Is there a recommended extension point to validate/reject an ApplicationConfigurationDto when IsAuthenticated=true and GrantedPolicies.Count==0?
  5. Are there patches after 10.0.2 related to intermittent empty permission resolution in RemotePermissionChecker / application configuration generation?

Notes

  • We can share anonymized log snippets with trace IDs if needed.
  • We already have diagnostics proving this is not always tied to Redis transport failures.

Thanks in advance. Any guidance on whether this is a known issue or recommended mitigation pattern would be very helpful.

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

31 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you share all logs.txt and identitymodel.txt to liming.ma@volosoft.com

    Thanks.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Your case may be caused by:

    HttpContextIdentityModelRemoteServiceHttpClientAuthenticator.Authenticate()
      → AccessTokenProvider.GetTokenAsync() returns null
      → Falls back to base.Authenticate()
      → IdentityModelAuthenticationService: client credentials grant
      → 825-char token, no 'sub', no 'session_id'
    

    We also reviewed your identitymodel.txt (March 20-23) — 58 out of 95 tokens are client credentials, generated every 30-90 minutes 24/7, including all day Saturday with zero users online. That's your Hangfire workers (CachePingWorker, EDT workers, health checks, etc.) running in the API Host process with no HttpContext — completely legitimate and unaffected by our changes.

    We've pushed updated code to the PR with three layers:

    Layer 1: Token backup + recovery (AccessTokenBackupMiddleware + AccessTokenBackupStore)

    A new middleware saves a copy of the user's access_token to Redis on every authenticated request. When the primary source (OIDC cookie) returns null, both the decorator and the authenticator attempt to recover from this backup. If the backup exists, the request proceeds normally with the recovered token — the user never sees missing menus.

    This is registered in the middleware pipeline:

    app.UseAuthentication();
    app.UseMiddleware<AccessTokenDiagnosticMiddleware>();
    app.UseMiddleware<AccessTokenBackupMiddleware>();  // saves token to Redis
    

    Layer 2: SafeRemoteServiceHttpClientAuthenticator (prevents client credentials fallback)

    Replaces ABP's HttpContextIdentityModelRemoteServiceHttpClientAuthenticator in the Web tier only. When the user's token is unavailable:

    1. Tries to recover from the Redis backup
    2. If backup found → uses it (request succeeds normally)
    3. If backup not found → skips authentication entirely (no client credentials token sent)

    Also detects Polly retry scenarios by checking if the Authorization header is already set when Authenticate() is called — this would indicate the request is being retried by the resilience handler.

    Layer 3: DiagnosticApplicationConfigurationClientDecorator guard (prevents cache poisoning)

    Last line of defense. If both primary token and backup are unavailable, blocks the remote API call and returns an empty AppConfig WITHOUT caching it. The page renders with reduced permissions for that single request only.

    Diagnostic output

    We still need to understand WHY the token goes missing. When the primary source fails, the code dumps:

    • All stored tokens in the OIDC session (access_token, refresh_token, id_token, expires_at)
    • Full AuthenticateAsync() result with Properties.Items
    • Cookie header size (to detect truncation)
    • HttpContext.User identity state
    • Thread ID (to detect async context loss)
    • Whether the request is a Polly retry

    After deploying, watch for these entries in the Web tier logs:

    | Log pattern | What it means | |---|---| | RECOVERED from backup | Token was missing from cookie but recovered from Redis — user experience preserved | | Both primary and backup token sources returned null | First request for a new user where no backup exists yet | | AUTH DUMP — Stored tokens | Shows exactly which tokens are present/missing in the OIDC session | | AUTH DUMP — Cookie header size | If unusually large, cookies may be getting truncated | | Authenticator — called again on a request that ALREADY has Authorization header | Polly retry detected | | Authenticator — BLOCKING client credentials fallback | Fallback prevented |

    Your Hangfire background jobs are unaffected — all changes are in CabMD.Web and CabMD.Web.Public only.

    You can try again and share the new logs.

    Thanks.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0

    Verified locally. Curious though, is this something that will get improved in abp - or is this due to something we're doing? That part I'm unsure about. For example, if you do make changes to abp and they get released - what should I be looking for in the release notes?

    In case it's helpful, this is what I used to cause the issue locally:

    /// <summary>
    ///     DEV-ONLY decorator that simulates intermittent access token loss by returning null
    ///     on a configurable percentage of requests. Used to verify the three-layer fix for [#10481](https://abp.io/QA/Questions/10481).
    ///     Enable via appsettings:
    ///     "Diagnostics": { "FaultyTokenProvider": { "Enabled": true, "FailurePercent": 30 } }
    ///     Safety: Registration is gated on IsDevelopment() in CabMDWebModule.ConfigureServices.
    /// </summary>
    [Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(IAbpAccessTokenProvider))]
    public class FaultyAccessTokenProviderDecorator : IAbpAccessTokenProvider, ITransientDependency
    {
        // Thread-safe random for failure rate calculation
        private static readonly Random s_random = new();
        private readonly IConfiguration _configuration;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly ILogger<FaultyAccessTokenProviderDecorator> _logger;
    
        public FaultyAccessTokenProviderDecorator(
            IHttpContextAccessor httpContextAccessor,
            IConfiguration configuration,
            ILogger<FaultyAccessTokenProviderDecorator> logger)
        {
            _httpContextAccessor = httpContextAccessor;
            _configuration = configuration;
            _logger = logger;
        }
    
        public async Task<string?> GetTokenAsync()
        {
            // Get the real token (same logic as ABP's HttpContextAbpAccessTokenProvider)
            var httpContext = _httpContextAccessor.HttpContext;
            if (httpContext == null)
            {
                return null;
            }
    
            var realToken = await httpContext.GetTokenAsync("access_token");
    
            // Check if fault injection is enabled (re-read config each time for hot-reload support)
            var enabled = _configuration.GetValue("Diagnostics:FaultyTokenProvider:Enabled", false);
            if (!enabled || string.IsNullOrEmpty(realToken))
            {
                return realToken;
            }
    
            var failurePercent = _configuration.GetValue("Diagnostics:FaultyTokenProvider:FailurePercent", 30);
            failurePercent = Math.Clamp(failurePercent, 0, 100);
    
            var roll = s_random.Next(100);
            if (roll >= failurePercent)
            {
                return realToken;
            }
    
            var userName = httpContext.User.Identity?.Name ?? "unknown";
            var path = httpContext.Request.Path.Value ?? "/";
    
            _logger.LogWarning(
                "ABP_DIAG: SIMULATED token null. " +
                "Real token was present ({TokenLength} chars) but returning null to simulate failure. " +
                "FailurePercent={FailurePercent}, Roll={Roll}, User={UserName}, Path={Path}",
                realToken.Length, failurePercent, roll, userName, path);
    
            return null;
        }
    }
    
    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    To answer your question: this is partly an ABP framework issue. HttpContextIdentityModelRemoteServiceHttpClientAuthenticator should not silently fall back to client credentials when ICurrentUser.IsAuthenticated is true — that fallback produces incorrect results for user-context endpoints regardless of why the token is unavailable. We'll look into improving this in the framework.

    However, we still need to understand why httpContext.GetTokenAsync("access_token") returns null for some authenticated requests in your environment. The backup/recovery mechanism works around the symptom, but we want to find the root cause. The latest code we pushed includes detailed diagnostic output (AUTH DUMP) that fires automatically when the token is missing — it dumps all stored tokens, the full AuthenticateAsync() result, authentication properties, and the cookie header size.

    Could you share the Web tier logs after the deployment has been running for a day or two with real users? Specifically we're looking for:

    • ABP_DIAG: AUTH DUMP entries — these reveal exactly what's present vs missing in the OIDC session when the token loss occurs
    • ABP_DIAG: RECOVERED from backup entries — confirms the backup mechanism is working and that the primary source did return null
    • ABP_DIAG: Authenticator entries at Warning/Error level — shows the state at the HTTP client authenticator layer

    This will tell us whether the issue is cookie-related (truncation, data protection), session-related (ticket store eviction), or something else entirely.

    Thanks.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0

    Hi maliming,

    We deployed the changes from your PR (SafeRemoteServiceHttpClientAuthenticator, AccessTokenBackupMiddleware, and the updated DiagnosticApplicationConfigurationClientDecorator with cache-poisoning guard) on March 27.

    Results after 3+ days in production (March 27-30):

    • Zero occurrences of ZERO POLICIES or IDENTITY MISMATCH — the GrantedPolicies=0 issue has not recurred
    • Zero occurrences of BLOCKING client credentials — the SafeRemoteServiceHttpClientAuthenticator has not needed to block a fallback
    • Zero occurrences of RECOVERED from backup — the backup recovery has never been triggered
    • Zero occurrences of AUTH DUMP — no authenticated user has experienced token loss
    • Zero occurrences of "access token is NULL" for an authenticated user

    The only access token is NULL entries (~20) are for anonymous/unauthenticated requests (IsAuthenticated: false, UserName: "unknown") on paths like /signalr-hubs/background-job-event/negotiate and /close-popup. These correctly fall through to client credentials as expected.

    Interpretation: Since deploying the three-layer fix, the intermittent token loss has not occurred at all in production. We haven't been able to capture an AUTH DUMP because the condition that triggers it (authenticated user + null token) hasn't happened. This suggests one of two possibilities:

    1. The fix has a preventive effect — perhaps the AccessTokenBackupMiddleware reading the token on every request is subtly changing timing/caching behaviour that was causing the original issue
    2. The issue is truly intermittent, and we simply haven't hit the right conditions yet in 3 days

    We'll continue monitoring. If/when the condition does occur, the AUTH DUMP and backup recovery logging will capture the full diagnostic state you need. Should we leave the diagnostics running indefinitely, or is there a specific timeframe you'd recommend?

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    To answer your question: it's a bit of both. ABP's silent fallback to client credentials when the user token is missing is a framework-level problem we should fix. But why the token goes null is environment-specific — most likely cookie truncation, Redis ticket eviction, or async context loss in a Polly retry.

    The AUTH DUMP will tell us which one. If it never triggers post-deployment, the cache poisoning fix is doing its job. Keep an eye on the logs for a few more days and let us know if the dump shows up.

    Thanks.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
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.