Concernant l’expiration glissante de l’authentification et de la session des formulaires ASP.NET

Nous avons une application ASP.NET 4.5 WebForms qui utilise les fonctionnalités d’authentification et de session des formulaires natifs. Les deux ont un délai d’attente de 20 minutes avec une expiration glissante.

Imaginez le scénario suivant. Un utilisateur a travaillé dans notre application pendant un certain temps, puis a effectué d’autres tâches, laissant notre application inactive pendant 20 minutes. L’utilisateur retourne ensuite à notre application pour rédiger un rapport. Toutefois, lorsque l’utilisateur tente de sauvegarder, il est traité avec l’écran de connexion et le rapport est perdu.

Évidemment, c’est indésirable. Au lieu de ce scénario, nous souhaitons que le navigateur soit redirigé vers la page de connexion dès que l’authentification ou la session a expiré. Pour ce faire, nous avons créé un service Web Api qui peut être appelé pour vérifier si tel est le cas.

public class SessionIsActiveController : ApiController { ///  /// Gets a value defining whether the session that belongs with the current HTTP request is still active or not. ///  /// True if the session, that belongs with the current HTTP request, is still active; false, otherwise./returns> public bool GetSessionIsActive() { CookieHeaderValue cookies = Request.Headers.GetCookies().FirstOrDefault(); if (cookies != null && cookies["authTicket"] != null && !ssortingng.IsNullOrEmpty(cookies["authTicket"].Value) && cookies["sessionId"] != null && !ssortingng.IsNullOrEmpty(cookies["sessionId"].Value)) { var authenticationTicket = FormsAuthentication.Decrypt(cookies["authTicket"].Value); if (authenticationTicket.Expired) return false; using (var asdc = new ASPStateDataContext()) // LINQ2SQL connection to the database where our session objects are stored { var expirationDate = SessionManager.FetchSessionExpirationDate(cookies["sessionId"].Value + ApplicationIdInHex, asdc); if (expirationDate == null || DateTime.Now.ToUniversalTime() > expirationDate.Value) return false; } return true; } return false; } } 

Ce service Web Api est appelé toutes les 10 secondes par le client pour vérifier si l’authentification ou la session a expiré. Si tel est le cas, le script redirige le navigateur vers la page de connexion. Cela fonctionne comme un charme.

Cependant, l’appel de ce service déclenche l’expiration glissante de l’authentification et de la session. Donc, essentiellement, créer une authentification et une session sans fin. J’ai défini un point d’arrêt au début du service pour vérifier si c’est l’une de nos propres fonctions qui le déclenche. Mais ce n’est pas le cas, cela semble se produire quelque part plus profondément dans ASP.NET, avant l’exécution du service.

  1. Existe-t-il un moyen de désactiver le déclenchement des expirations glissantes de l’authentification et de la session d’ASP.NET pour une requête spécifique?
  2. Sinon, quelle est la meilleure pratique pour affronter un tel scénario?

  1. Cela semble être impossible. Une fois que l’expiration glissante est activée, elle est toujours déclenchée. S’il existe un moyen d’accéder à la session sans l’étendre, nous n’avons pas été en mesure de le trouver.

  2. Alors, comment aborder ce scénario? Nous avons proposé la solution alternative suivante à celle proposée à l’origine dans la question. Celui-ci est en réalité plus efficace, car il n’utilise pas de service Web pour téléphoner à la maison toutes les x secondes.

Nous voulons donc avoir un moyen de savoir quand l’authentification par formulaire d’ASP.NET ou la session est arrivée à expiration, afin de pouvoir déconnecter de manière proactive l’utilisateur. Une simple timer javascript sur chaque page ( telle que proposée par Khalid Abuhakmeh) ne suffirait pas, car l’utilisateur pourrait utiliser l’application simultanément dans plusieurs fenêtres / tabs du navigateur.

La première décision que nous avons prise pour simplifier ce problème consiste à augmenter de quelques minutes le délai d’expiration de la session par rapport au délai d’expiration de l’authentification par formulaire. De cette façon, la session n’expirera jamais avant l’authentification par formulaires. Si une ancienne session est en attente lors de la prochaine tentative de connexion de l’utilisateur, nous l’abandonnons pour en forcer une nouvelle.

Très bien, il ne rest plus qu’à prendre en compte l’expiration de l’authentification par formulaires.

Ensuite, nous avons décidé de désactiver l’expiration automatique du glissement de l’authentification par formulaires (comme indiqué dans le fichier web.config) et d’en créer notre propre version.

 public static void RenewAuthenticationTicket(HttpContext currentContext) { var authenticationTicketCookie = currentContext.Request.Cookies["AuthTicketNameHere"]; var oldAuthTicket = FormsAuthentication.Decrypt(authenticationTicketCookie.Value); var newAuthTicket = oldAuthTicket; newAuthTicket = FormsAuthentication.RenewTicketIfOld(oldAuthTicket); //This sortingggers the regular sliding expiration functionality. if (newAuthTicket != oldAuthTicket) { //Add the renewed authentication ticket cookie to the response. authenticationTicketCookie.Value = FormsAuthentication.Encrypt(newAuthTicket); authenticationTicketCookie.Domain = FormsAuthentication.CookieDomain; authenticationTicketCookie.Path = FormsAuthentication.FormsCookiePath; authenticationTicketCookie.HttpOnly = true; authenticationTicketCookie.Secure = FormsAuthentication.RequireSSL; currentContext.Response.Cookies.Add(authenticationTicketCookie); //Here we have the opportunity to do some extra stuff. SetAuthenticationExpirationTicket(currentContext); } } 

Nous appelons cette méthode à partir de l’événement OnPreRenderComplete dans la classe BasePage de notre application, à partir de laquelle toutes les autres pages héritent. Il fait exactement la même chose que la fonctionnalité d’expiration normale, mais nous avons la possibilité de faire des choses supplémentaires; like, appelez notre méthode SetAuthenticationExpirationTicket .

 public static void SetAuthenticationExpirationTicket(HttpContext currentContext) { //Take the current time, in UTC, and add the forms authentication timeout (plus one second for some elbow room ;-) var expirationDateTimeInUtc = DateTime.UtcNow.AddMinutes(FormsAuthentication.Timeout.TotalMinutes).AddSeconds(1); var authenticationExpirationTicketCookie = new HttpCookie("AuthenticationExpirationTicket"); //The value of the cookie will be the expiration date formatted as milliseconds since 01.01.1970. authenticationExpirationTicketCookie.Value = expirationDateTimeInUtc.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds.ToSsortingng("F0"); authenticationExpirationTicketCookie.HttpOnly = false; //This is important, otherwise we cannot resortingeve this cookie in javascript. authenticationExpirationTicketCookie.Secure = FormsAuthentication.RequireSSL; currentContext.Response.Cookies.Add(authenticationExpirationTicketCookie); } 

Nous disposons maintenant d’un cookie supplémentaire qui représente toujours le délai d’expiration correct de l’authentification par formulaire, même si l’utilisateur travaille dans différentes fenêtres / différents tabs du navigateur. Après tout, les cookies ont une scope étendue à la navigation. Il ne rest maintenant qu’une fonction javascript pour vérifier la valeur du cookie.

 function CheckAuthenticationExpiration() { var c = $.cookie("AuthenticationExpirationTicket"); if (c != null && c != "" && !isNaN(c)) { var now = new Date(); var ms = parseInt(c, 10); var expiration = new Date().setTime(ms); if (now > expiration) location.reload(true); } } 

(Notez que nous utilisons jQuery Cookie Plugin pour récupérer le cookie.)

Placez cette fonction dans un intervalle et les utilisateurs seront déconnectés dès que l’authentification de leurs formulaires aura expirée. Voilà 🙂 Un avantage supplémentaire de cette implémentation est que vous avez maintenant le contrôle sur le moment où l’expiration de l’authentification par formulaire est étendue. Si vous voulez un ensemble de services Web qui ne prolongent pas l’expiration, n’appelez pas la méthode RenewAuthenticationTicket pour eux.

S’il vous plaît laissez un commentaire si vous avez quelque chose à append!

Tout cela peut être résolu côté client, sans qu’il soit nécessaire de retourner sur le serveur.

Faites ceci en JavaScript.

  var timeout = setTimeout(function () { window.location = "/login"; }, twentyMinutesInMilliseconds + 1); 

Le délai d’attente sera défini sur 20 minutes à chaque actualisation de page. Cela garantit que l’utilisateur doit effectuer tout son travail avant l’expiration du délai. De nombreux sites utilisent cette méthode, ce qui vous évite de faire des requêtes inutiles au serveur.

Les fonctionnalités de votre site Web doivent fonctionner sans JavaScript ou vous devez simplement remplacer un problème par un autre. J’ai également abordé ce problème et voici comment il a été résolu:

Lorsque vous vous authentifiez, un cookie de session est créé avec une durée de vie par défaut de 20 min. À l’expiration de ce délai, l’utilisateur sera déconnecté.

image de cookie

Lorsque l’utilisateur sélectionne “Remember me” dans le formulaire de connexion, un cookie de persistance supplémentaire [AuthCookie] est créé dans le côté client et dans la firebase database. Ce cookie a une durée de vie de 1 mois. Chaque fois que la page est chargée, les données de cookie de session et de persistance sont recréées avec une nouvelle durée de vie (normalement, vous souhaitez déchiffrer / chiffrer le ticket).

Imaginez le scénario suivant. Un utilisateur a travaillé dans notre application pendant un certain temps, puis a effectué d’autres tâches, laissant notre application inactive pendant 20 minutes. L’utilisateur retourne ensuite à notre application pour rédiger un rapport. Lorsque l’utilisateur tente de sauvegarder, sa session est restaurée avant la demande.

Une solution consiste à étendre global.aspx à la gestion de la requête préalable. Quelque chose dans les lignes de:

  void application_PreRequestHandlerExecute(object sender, EventArgs e){ ... if (HttpContext.Current.Handler is IRequiresSessionState) { if (!context.User.Identity.IsAuthenticated) AuthService.DefaultProvider.AuthenticateUserFromExternalSource(); 

AuthenticateUserFromExternalSource doit vérifier si les données de cookie correspondent à celles de la firebase database, car tout élément stocké du côté client peut être modifié. Si vous avez des services payants avec des droits d’access, vous devez vérifier si l’utilisateur dispose toujours de ces droits, puis vous pouvez recréer la session.