- 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
CheckExpiresAt
withAddCookie
.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; } }
-
0
@maliming Sadly it dd not work.
-
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?
-
0
hi
Please share the full logs of your backend app, including the authserver and web app.
liming.ma@volosoft.com
-
0
sent. Thanks
-
0
the auth server goes in an endless loop.
hi
The current user doesn't have the
Cssea.Cetrs
permission, 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.Cetrs
-
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
-
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
-
0
the site is not accessible externally. Are you available for zoom?
-
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 });
-
0
hi
options.SaveTokens = true; //this must set as true.
-
0
I can check it by zoom.
-
0
Whenever you are ready. You can send the link to my email you have it. I am on standby now
-
0
Please share your screen, Thanks https://us05web.zoom.us/j/86851103486?pwd=UkFBQzN5OWZLRkdwZzRlS3VtZWJodz09
-
0
hi
I will share some code with you, then you can test it on production.
-
0
sure i will wait for your code .. this is a production issue so I am thankful for the help
-
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}"; } }
-
0
These codes will write the Error logs. Please share the logs of web host.
Thanks
liming.ma@volosoft.com
-
0
just to be clear. the WEB or HOST? You said web.host and the namespace says .web
I think you mean to the HOST project
I will add and deploy now
-
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"); });
-
0
Web. ok
-
0
Did you solved this odd behavior? I have the same issue like you
-
0
I will update my code Then you can retry.
Wait a sec.
-
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
-
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.