0

I've been handed a requirement to force a re-authorization of a user, when a new month starts (we have a monthly subscription model). To implement this and to NOT influence other authentication providers used in our application, I've modified the sign in call as follows:

Before:

await HttpContext.SignInAsync(authorizationProvider.GetClaimsPrincipal());

After:

await HttpContext.SignInAsync(authorizationProvider.GetClaimsPrincipal(), authorizationProvider.GetAutheticationProperties());

public AuthenticationProperties GetAutheticationProperties() =>
   new AuthenticationProperties
   {
      ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(1),
      IsPersistent = true,
      AllowRefresh = false
   };

Note that ExpiresUtc is being set to 1min in the future for testing purposes; it will be computed to the end of the month once the implementation works.

I've expected that our identity (extending ClaimsIdentity) would get its propererty IsAuthorized false after the ExpiresUtc has been reached, but it doesn't. Reading ClaimsIdentity.IsAuthenticated it states

true if the identity has been authenticated; otherwise, false. Remarks: true if the AuthenticationType property is not null or an empty string.

Now I'm confused.. What shall I expect after expiry occurred? Respectively, how does the identity become IsAuthenticated == false?

We currently use asp.net core 2.2 and are in the process of migration to 3.

Authentication is being registered like this on application startup (Startup.cs):

        var expirationInMinutes = Convert.ToInt32(Configuration["Authentication:ExpirationInMinutes"]);
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                // Configure cookie authentication
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o =>
                                                                              {
                                                                                  o.LoginPath = new PathString("/Account/LogOn");
                                                                                  o.LogoutPath = new PathString("/Account/LogOut");
                                                                                  o.AccessDeniedPath = new PathString("/Account/AccessDenied");
                                                                                  o.SlidingExpiration = true;
                                                                                  o.ExpireTimeSpan = TimeSpan.FromMinutes(expirationInMinutes);
                                                                                  o.Cookie.Expiration = TimeSpan.FromMinutes(expirationInMinutes);
                                                                                  o.EventsType = typeof(CustomCookieAuthenticationEvents);

                                                                                  // Used because of safari
                                                                                  o.CookieManager = new ChunkingCookieManager()
                                                                                                    {
                                                                                                        ChunkSize = 2048,
                                                                                                        ThrowForPartialCookies = true
                                                                                                    };
                                                                              });
Philippe
  • 1,949
  • 4
  • 31
  • 57

2 Answers2

1

When the user is authenticated, a ticket is issued. To save on DB queries, that ticket is revalidated on an interval (20 minutes by default). When the auth ticket has expired, that won't actually take effect until the ticket is revalidated, so if your validation interval is 20 minutes, they will remain logged in for at least 20 minutes, regardless of what you set the expiration to.

You can lower this interval, all the way down to zero if you like, which effectively means the ticket would be revalidated with every request. However, the lower the interval, the more queries your DB will have to field, so it's a trade-off. To change the interval, just add:

services.Configure<SecurityStampValidatorOptions>(c =>
{
    c.ValidationInterval = TimeSpan.FromMinutes(1);
});
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Oh, good thing to know, thanks! Waiting 20min didn't log me out. Is my expectation to NOT get the expired identity back when calling _httpContext.User.Identities correct? – Philippe Jan 07 '20 at 14:14
  • If the user has been truly signed out, then there will be no `HttpContext.User`. – Chris Pratt Jan 07 '20 at 14:19
  • Why are you talking about DB queries? Should ticket be validated without dB call, is it expired or not? – Michael Freidgeim Aug 23 '21 at 11:48
  • Also according to https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.securitystampvalidatoroptions.validationinterval?view=aspnetcore-5.0 default is 30 min – Michael Freidgeim Aug 23 '21 at 13:26
0

Another reason of not expiring on time as expected can be ClockSkew. By default your token will be valid up to 5 extra minutes.

See JWT Token authentication, expired tokens still working, .net core Web Api

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170