- ABP Framework version: v7.1.0
- UI type: MVC
- DB provider: EF Core
- Tiered (MVC) or Identity Server Separated (Angular): yes
-
- Exception message and stack trace:
- Steps to reproduce the issue:"
This is happening in production in multiple applications.
I may be doing something wrong so please be patient and let me know what I can do to fix it.
- The user is logged in. (image 1)

- The session timeout after x mins (expected)
- When the user comes back the page is still on image 1. Clicks anywhere. Notice that the menu is removed (permissions are gone because of the user no longer logged in. Expected)
- However, notice that now the user is still showing up in the top right corner (admin). (Image 2)

- I logged out
- I log in again.
- Still shows up as if the current user has no permission to do anything (the user is admin and has full permissions) (Image 2)

- Sometimes if the user times out on a page and clicks a link after a few hours the auth server goes into an endless loop

This is a production issue. I appreciate some help/direction.
Thank you
25 Answer(s)
-
0
hi
Please try using
CheckExpiresAtwithAddCookie.context.Services.AddAuthentication(options => { //... }) .AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); options.CheckExpiresAt(); }) .AddAbpOpenIdConnect("oidc", options => { //... });using System; using System.Globalization; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; namespace MyCompanyName.MyProjectName.Web; public static class CookieAuthenticationOptionsExtensions { public static CookieAuthenticationOptions CheckExpiresAt(this CookieAuthenticationOptions options) { var originalHandler = options.Events.OnValidatePrincipal; options.Events.OnValidatePrincipal = async principalContext => { originalHandler?.Invoke(principalContext); if (principalContext.Principal != null && principalContext.Principal.Identity != null && principalContext.Principal.Identity.IsAuthenticated) { var tokenExpiresAt = principalContext.Properties.Items[".Token.expires_at"]; if (tokenExpiresAt != null && DateTimeOffset.TryParseExact(tokenExpiresAt, "yyyy-MM-ddTHH:mm:ss.fffffffzzz", null, DateTimeStyles.AdjustToUniversal, out var expiresAt) && expiresAt < DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(5))) { principalContext.RejectPrincipal(); await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); } } }; return options; } }Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
sorry to say but when things get stuck the application becomes useless :( this is production and now even I delete cookies I flush redis I restart the service/the application even restarted production server. no matter what I do the logged in admin now only sees home.
if (after login as admin) I try to manually type in a secure page, the auth server goes in an endless loop.
What should I do?
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
the auth server goes in an endless loop.
hi
The current user doesn't have the
Cssea.Cetrspermission, so the endless loop happened.We will avoid this problem in the next version.
Request starting HTTP/2 GET https://apps.cssea.bc.ca/Cetrs - - These requirements were not met: PermissionRequirement: Cssea.CetrsMarkdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
no matter what I do the logged in admin now only sees home.
Can you share a username and password?
I will test it online.
liming.ma@volosoft.com
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Actually the user has access to everything
This is misleading and not correct:
Request starting HTTP/2 GET https://apps.cssea.bc.ca/Cetrs - - These requirements were not met: PermissionRequirement: Cssea.Cetrs
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
by the way, this is what Web project is using:
context.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); options.CheckExpiresAt(); }) .AddAbpOpenIdConnect("oidc", options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); // true options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.ClientId = configuration["AuthServer:ClientId"]; options.ClientSecret = configuration["AuthServer:ClientSecret"]; options.UsePkce = true; options.SaveTokens = false; // I tried true or false options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("roles"); options.Scope.Add("email"); options.Scope.Add("phone"); options.Scope.Add("api"); // the API client name });Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
hi
Add below code to your Web.Host project
using System.Threading.Tasks; using IdentityModel.Client; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client; using Volo.Abp.Http.Client.Authentication; using Volo.Abp.Http.Client.IdentityModel.Web; using Volo.Abp.IdentityModel; namespace MyCompanyName.MyProjectName.Web; [Dependency(ReplaceServices = true)] public class MyHttpContextIdentityModelRemoteServiceHttpClientAuthenticator : HttpContextIdentityModelRemoteServiceHttpClientAuthenticator { public ILogger<MyHttpContextIdentityModelRemoteServiceHttpClientAuthenticator> Logger { get; set; } public MyHttpContextIdentityModelRemoteServiceHttpClientAuthenticator(IIdentityModelAuthenticationService identityModelAuthenticationService, ILogger<MyHttpContextIdentityModelRemoteServiceHttpClientAuthenticator> logger) : base(identityModelAuthenticationService) { Logger = logger; } public async override Task Authenticate(RemoteServiceHttpClientAuthenticateContext context) { if (context.RemoteService.GetUseCurrentAccessToken() != false) { var accessToken = await GetAccessTokenFromHttpContextOrNullAsync(); if (accessToken != null) { context.Request.SetBearerToken(accessToken); return; } } await base.Authenticate(context); } protected async override Task<string> GetAccessTokenFromHttpContextOrNullAsync() { var httpContext = HttpContextAccessor?.HttpContext; if (httpContext == null) { Logger.LogError("Could not get HttpContext!"); return null; } var token = await httpContext.GetTokenAsync("access_token"); if (token.IsNullOrEmpty()) { Logger.LogError("Could not get access_token!"); return null; } Logger.LogError("access_token: " + token); return token; } }using System.Globalization; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; using Volo.Abp.AspNetCore.Mvc.Client; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Threading; using Volo.Abp.Users; namespace MyCompanyName.MyProjectName.Web; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(ICachedApplicationConfigurationClient))] public class MyMvcCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency { public ILogger<MvcCachedApplicationConfigurationClient> Logger { get; set; } protected IHttpContextAccessor HttpContextAccessor { get; } protected AbpApplicationConfigurationClientProxy ApplicationConfigurationAppService { get; } protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; } protected ICurrentUser CurrentUser { get; } protected IDistributedCache<ApplicationConfigurationDto> Cache { get; } protected AbpAspNetCoreMvcClientCacheOptions Options { get; } public MyMvcCachedApplicationConfigurationClient( IDistributedCache<ApplicationConfigurationDto> cache, AbpApplicationConfigurationClientProxy applicationConfigurationAppService, ICurrentUser currentUser, IHttpContextAccessor httpContextAccessor, AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy, IOptions<AbpAspNetCoreMvcClientCacheOptions> options, ILogger<MvcCachedApplicationConfigurationClient> logger) { ApplicationConfigurationAppService = applicationConfigurationAppService; CurrentUser = currentUser; HttpContextAccessor = httpContextAccessor; ApplicationLocalizationClientProxy = applicationLocalizationClientProxy; Logger = logger; Options = options.Value; Cache = cache; } public async Task<ApplicationConfigurationDto> GetAsync() { var cacheKey = CreateCacheKey(); var httpContext = HttpContextAccessor?.HttpContext; if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration) { return configuration; } configuration = await Cache.GetOrAddAsync( cacheKey, async () => await GetRemoteConfigurationAsync(), () => new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = Options.ApplicationConfigurationDtoCacheAbsoluteExpiration } ); if (httpContext != null) { httpContext.Items[cacheKey] = configuration; } Logger.LogError(JsonSerializer.Serialize(configuration, new JsonSerializerOptions() { WriteIndented = true })); return configuration; } private async Task<ApplicationConfigurationDto> GetRemoteConfigurationAsync() { var config = await ApplicationConfigurationAppService.GetAsync( new ApplicationConfigurationRequestOptions { IncludeLocalizationResources = false } ); var localizationDto = await ApplicationLocalizationClientProxy.GetAsync( new ApplicationLocalizationRequestDto { CultureName = config.Localization.CurrentCulture.Name, OnlyDynamics = true } ); config.Localization.Resources = localizationDto.Resources; return config; } public ApplicationConfigurationDto Get() { var cacheKey = CreateCacheKey(); var httpContext = HttpContextAccessor?.HttpContext; if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration) { return configuration; } return AsyncHelper.RunSync(GetAsync); } protected virtual string CreateCacheKey() { return MvcCachedApplicationConfigurationClientHelper.CreateCacheKey(CurrentUser); } } static class MvcCachedApplicationConfigurationClientHelper { public static string CreateCacheKey(ICurrentUser currentUser) { var userKey = currentUser.Id?.ToString("N") ?? "Anonymous"; return $"ApplicationConfiguration_{userKey}_{CultureInfo.CurrentUICulture.Name}"; } }Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
The project that is using
context.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); }) .AddAbpOpenIdConnect("oidc", options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.ClientId = configuration["AuthServer:ClientId"]; options.ClientSecret = configuration["AuthServer:ClientSecret"]; options.UsePkce = true; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("roles"); options.Scope.Add("email"); options.Scope.Add("phone"); options.Scope.Add("MyProjectName"); });Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
@maliming
Thank you. so far; The changes seem to work. I will continue testing today.
I would like to ask though; why that code for the CheckExpiresAt() not in the core ABP? Is there any other side effect?
Also should I use the same code for the API HOST project?
For reference to others having the issue I believe the issue is that the auth cookie not expiring with the session expiration.
The fix that was suggested (and seems working) by maliming):
in the WEB project Module:
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); options.CheckExpiresAt(); // << ADDED })and added a file/class CookieAuthenticationOptionsExtensions.cs in the web module with the class below:
using System; using System.Globalization; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; namespace { your namespace }.Web.Extensions { public static class CookieAuthenticationOptionsExtensions { public static CookieAuthenticationOptions CheckExpiresAt(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc") { var originalHandler = options.Events.OnValidatePrincipal; options.Events.OnValidatePrincipal = async principalContext => { originalHandler?.Invoke(principalContext); if (principalContext.Principal != null && principalContext.Principal.Identity != null && principalContext.Principal.Identity.IsAuthenticated) { var tokenExpiresAt = principalContext.Properties.Items[".Token.expires_at"]; if (tokenExpiresAt != null && DateTimeOffset.TryParseExact(tokenExpiresAt, "yyyy-MM-ddTHH:mm:ss.fffffffzzz", null, DateTimeStyles.AdjustToUniversal, out var expiresAt) && expiresAt < DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(5))) { principalContext.RejectPrincipal(); await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); } } }; return options; } } }Thank you
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
I would like to ask though; why that code for the CheckExpiresAt() not in the core ABP? Is there any other side effect?
We will do that. https://github.com/abpframework/abp/pull/16504
Also should I use the same code for the API HOST project?
No. This only needs for the web project.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)