I have a Blazor server app that uses Cookies as the default scheme and OpenIdConnect as the challenge scheme. The app also uses a SQL Server distributed cache for the ITicketStore implementation. The access token has a lifetime of 48 hours and the refresh token 30 days.
Cookie setup is:
services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme) .Configure<ITicketStore>((options, store) => { options.ExpireTimeSpan = TimeSpan.FromDays(14); options.SlidingExpiration = true; options.SessionStore = store; });And authentication:
services.AddAuthentication(options =>{ options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme).AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>{ options.Authority = authority; options.ClientId = clientId; options.ClientSecret = clientSecret; options.ResponseType = OpenIdConnectResponseType.Code; options.ResponseMode = OpenIdConnectResponseMode.FormPost; options.GetClaimsFromUserInfoEndpoint = true; options.MapInboundClaims = false; options.SaveTokens = true; options.UseTokenLifetime = false; options.UseSecurityTokenValidator = true; options.Scope.Add(OpenIdConnectScope.OpenIdProfile); options.Scope.Add(OpenIdConnectScope.Email); options.Scope.Add(OpenIdConnectScope.OfflineAccess); options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = "role" };});Sign in is:
public async Task OnGet(string redirectUri){ await HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = redirectUri, IsPersistent = true, });}Everytime the user accesses the app, the cookie is recreated and a new item is added in the cache.
If I am setting the cookie to persist and has a lifetime of 14 days, why is it being recreated and an authentication ticket is added every time to the cache?
UPDATE
The ITicketStore implementation:
public class AuthenticationTicketStore( IDistributedCache cache, ILogger<AuthenticationTicketStore> logger) : ITicketStore{ private const string KeyPrefix = "AuthSessionStore-"; private readonly TicketSerializer ticketSerializer = TicketSerializer.Default; public async Task<string> StoreAsync(AuthenticationTicket ticket) { var key = $"{KeyPrefix}{Guid.NewGuid():N}"; await RenewAsync(key, ticket); return key; } public Task RenewAsync(string key, AuthenticationTicket ticket) { if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } var options = new DistributedCacheEntryOptions(); var expiresUtc = ticket.Properties.ExpiresUtc; if (expiresUtc.HasValue) { options.SetAbsoluteExpiration(expiresUtc.Value); } if (ticket.Properties.AllowRefresh ?? false) { options.SetSlidingExpiration(TimeSpan.FromMinutes(60)); } return cache.SetAsync(key, ticketSerializer.Serialize(ticket), options); } public async Task<AuthenticationTicket> RetrieveAsync(string key) { var value = await cache.GetAsync(key); return value != null ? ticketSerializer.Deserialize(value) : null; } public Task RemoveAsync(string key) => cache.RemoveAsync(key);}