LA SAGA 6: les cookies et les tickets d'authentification
Par amethyste le Sep 1, 2007 | Dans focus | Réagir »
Le thème de ce blog sera l'authentification par formulaire et les cookies d'authentification que l'on peut d'ailleurs utiliser dans tout autre mode d'authentification à vrai dire.
De tous les modèles d'authentification disponibles depuis ASP, l'authentification par formulaire est celui qui a le plus évolué. Dans cet article je ne vais parler que des méthodes "à l'ancienne" et réserverai un blog spécifique aux nouveautés. Je pense utile que bien comprendre comment fonctionnent les choses, surtout lorsque l'on parle de sécurité.
ASP.NET prend en charge ce modèle à travers la classe FormsAuthentification [4] dont les méthodes intéressantes sont static.
Quel que soit votre scénario, les étapes sont les même:
1. Créer un formulaire de login
2. Ajouter une zone de saisie de l'identifiant
3. Ajouter une zone de saisie du mot de passe
4. Ajouter un bouton de connexion
5. Ajouter la logique de validation de la saisie
6. Ajouter la logique de continuation vers la page initialement réclamée
7. Paramétrer le site pour activer le modèle d'authentification
Le paramétrage du site s'effectue bien sûr dans le fichier de configuration, par exemple:
<authentication mode="Forms"> <forms loginUrl="~/login/login.aspx"> </forms> </authentication> <authorization> <deny users="?"/> </authorization>
Pour valider les saisies de l'utilisateur on doit disposer d'une base de données de credentials. ASP.NET fournit un mécanisme intégré lorsque ceux-ci sont situés dans le fichier de configuration web.config:
<authentication mode="Forms"> <forms loginUrl="~/login/login.aspx"> <credentials> <user name="amethyste" password="amethyste"/> </credentials> </forms> </authentication>
Le code de connexion serait alors quelque chose comme:
bool Resultat = FormsAuthentication.Authenticate(this.Identifiant.Text, this.MotPasse.Text); if (Resultat) { FormsAuthentication.RedirectFromLoginPage( "nnn", true); } else { FormsAuthentication.RedirectToLoginPage(); }
Authenticate va lire le contenu du fichier de configuration, le comparer avec les saisies effectuées et retourner le résultat du chalenge sous la forme d'un booléen. Notez plusieurs choses:
• L'appel à RedirectToLoginPage n'est pas nécessaire, c'est juste pour montrer l'existence d'une méthode peu connue!
• Il est possible de crypter les mots de passe, Authenticate en tient évidemment compte.
Dans les coulisse Authenticate incrémente aussi des compteurs de performance pour comptabiliser les logins qui échouent ou réussissent.
Placer les credentials dans le fichier de configuration n'est pas gérable pour une application ambitieuse. En général on fait ceci dans différents conteneurs comme un fichier XML [1], une base de données [2] ou Active Directory [3].
Dans tous les cas il vous appartiendra de fournir une autre implémentation d'Authenticate.
Comment fait ASP pour savoir que l'utilisateur est authentifié la prochaine fois qu'il présente une requête sur le site?
L'examen des cookies avant/après l'authentification montre que .ASPXAUTH est apparu avec les informations suivantes (les vôtre peuvent être différentes):
Nom: .ASPXAUTH
Valeur: 246BB2C90405A…89589FC09F9D27A8
Hôte: localhost
Chemin: /
Sécurisé: Non
Date d'expiration: Fri, 31 Aug 2007 22:14:54 GMT
Ce cookie, appelé cookie d'authentification, est créé lors de l'appel à la méthode RedirecteToLoginPage. On peut agir sur les différents paramètres du cookie (nom, chemin, stratégie d'expiration..) à partir des attributs de la section <forms> ou bien au niveau du code à l'aide des différentes surcharges de la méthode, mais aussi en créant à la main ce cookie, nous verrons plus tard comment.
La valeur contient des informations sur l'authentification en cours. Elles sont cryptées en utilisant les éléments de configuration de la section <machineKey>. Ces informations constituent ce que l'on appelle un ticket d'authentification. Un cookie d'authentification est donc le cookie qui contient le ticket d'authentification.
On peut effacer ce cookie par un appel à FormsAuthentication.SignOut ou bien, manuellement, en effaçant les cookies depuis son navigateur.
L'analyse avec Fiddler du trafic généré lors du processus d'authentification montre les séquences suivantes:
1. Le client envoie une requête GET à Default.aspx. Aucun cookie d'authentification n'est envoyé
2. Le serveur retourne un code 302 pour demander au navigateur de réclamer la page Login.aspx
3. Le client envoie un POST vers Login.aspx contenant les informations d'authentification (identifiant et mot de passe)
4. Le serveur envoie à nouveau un 302 vers Default.aspx. Il émet aussi le cookie d'authentification
5. Le client répond par un GET vers Default.aspx et émet lui aussi le cookie d'authentification
Le cookie d'authentification fait partie de toutes les requêtes tant que le client est authentifié.
On peut profiter de la validation des informations d'authentification fournies par l'utilisateur du site pour rechercher ses rôles. Il paraît naturel de les placer eux aussi dans le ticket.
On va donc créer un ticket d'authentification à la main, puis le sérialiser dans le cookie. Le ticket d'authentification est modélisé par la classe FormsAuthenticationTicket qui fournit également toute les méthodes utiles pour alimenter le cookie d'authentification.
On peut alors compléter le code précédent de la façon suivante:
if ( FormsAuthentication.Authenticate( this.Identifiant.Text, this.MotPasse.Text)) { // l'authentification à réussie, //on créé un cookie non persistant FormsAuthenticationTicket Ticket = new FormsAuthenticationTicket( 1, // version this.Identifiant.Text, // login DateTime.Now, // date de création DateTime.Now.AddMinutes(20),// expiration au bout de 20 minutes false, // dans un cookie non persistant "Ecrivain", // on sauvegarde les rôles dans le ticket FormsAuthentication.FormsCookiePath); // crypte le ticket string EncryptedTicket = FormsAuthentication.Encrypt(Ticket); // création d'un cookie d'authentification contenant le ticket HttpCookie Cookie = new HttpCookie(FormsAuthentication.FormsCookieName, EncryptedTicket); // ajoute le cookie dans la collection de cookies Response.Cookies.Add(Cookie); string RedirectUrl = FormsAuthentication.GetRedirectUrl(this.Identifiant.Text, false); Response.Redirect(RedirectUrl); }
Les remarques sont nombreuses.
D'abord beaucoup des exemples rencontrés sur Internet font intervenir un appel à FormsAuthentication.SetAuthCookie en début de code.
A mon avis c'est inutile. SetAuthCookie permet de créer un cookie d'authentification du genre de celui créé par défaut lors d'un appel à RedirectFromLoginPage. Ce n'est justement pas ce que l'on souhaite faire et de toute façon ce cookie sera écrasé quelques lignes plus loin.
Il existe aussi une méthode GetAuthCookie destinées à créer un cookie d'authentification. La différence avec SetAuthCookie est que cette dernière ajoute le cookie à la collection de cookies de la réponse. Elle appelle d'ailleurs GetAuthCookie en interne.
Le premier paramètre du constructeur de FormsAuthenticationTicket est un très mystérieux numéro de version de cookie dont j'ignore l'usage.
L'avant dernier paramètre est un paramètre du nom d'userData. J'y place le rôle, mais on peut y placer ce que l'on souhait en fait. C'est l'application qui donne du sens à ce paramètre.
La suite ne pose pas de problèmes notables si ce n'est que l'on peut se demander pourquoi ne pas utiliser RedirectFromLoginPage.
La raison est que cette méthode effectue un appel à SetAuthCookie et va par conséquent écraser notre beau ticket tout neuf!
Notez une curiosité: le dernier paramètre de GetRedirectUrl est actuellement inutilisé d'après la documentation. Ceci étant cela peut changer à tout moment. Donc placez donc une valeur cohérente avec votre ticket à tout hasard…
Dernier point: la taille du cookie. Celle-ci est limitée à 4096 octets et diverses autres limitations [6]. Lorsque je dis qu'userData peut contenir ce que l'on veut, il y a des limites tout de même.
La date d'expiration peut ne pas être absolue, mais glissante. On place le paramètre slidingExpiration à true dans le fichier de configuration
Tant que le ticket n'a pas expiré, il est alors possible de le renouveler manuellement à l'aide de la méthode FormsAuthenticationTicket.RenewTicketIfOld.
C'est pas tout à fait terminé, il faut maintenant relire notre ticket afin d'en extraire les rôles. Comme toujours la chose se passe dans global.asax et l'événement Authentication_Request:
string NomCookie = FormsAuthentication.FormsCookieName; HttpCookie Cookie =Context.Request.Cookies[NomCookie]; if (Cookie == null) { // pas authentifié return; } FormsAuthenticationTicket Ticket = null; try { Ticket = FormsAuthentication.Decrypt(Cookie.Value); } catch { return; } if (Ticket == null) { return; } string Role = Ticket.UserData; // création du principal ....
On termine en créant un Principal de la manière vue la semaine dernière.
Une limitation importante du cookie est sa taille. Que faire si l'on ne peut place les informations utiles pour reconstituer le Principal dans le cookie?
Il y a 2 types d'options:
1. Crée le Principal dans le formulaire d'authentification, le placer en session puis créer un cookie d'authentification normal en lançant RedirectFromLoginPage
2. Sérialiser le principal dans une base de données ou tout autre conteneur et créer un ticket dont le userData contient l'Id en base du Principal
On va en rester là pour cette fois, la semaine prochaine on abordera des scénarios spéciaux:
• Que faire avec les navigateurs qui n'acceptent pas les cookies
• Authentification multi site
• Authentification entre ASP .NET 1.1 et 2.0
Note d'ailleurs
Vous savez certainement qu'une des particularités de l'eau est de voir son volume augmenter lorsqu'elle gèle, tout au moins dans les conditions de pression ambiantes. Mais elle n'est pas la seule puisque cette propriété est aussi partagée par diverses substances comme le bismuth, le gallium, l'antimoine, le silicium ou encore l'acide acétique.
Je me souviens d'un de mes bouquins de chimie qui montrait une image d'une personne tenant un bloc de gallium dans sa main. Celui-ci avait entièrement fondu. Le gallium est en effet un métal dont le point de fusion est de 30°C.
Depuis j'ai acheté un échantillon de cet élément pour ma collection personnelle [7] et évidemment la première chose que j'ai essayé est de reproduire cette expérience…, mais vainement.
J'ai appris depuis que la température de la peau est de 25°C en général. C'est la température de l'eau des piscines justement pour éviter les chocs thermiques lorsque l'on y plonge.
Une dernière anecdote amusante, toujours au sujet de l'eau. Sa densité varie avec la température et atteint son maximum pour 4°C. C'est justement une des raisons qui font que le fond des océans est plus chaud que l'on pourrai croire malgré l'absence de lumière et rend possible la vie aquatique durant l'hiver.
Bibliographie
[1] Scénario authentification par formulaire et le conteneur est un fichier XML:
http://www.devcity.net/Articles/53/1/formauth_xml.aspx
[2] Scénario authentification par formulaire et le conteneur est une basse de données:
[3] Scénario authentification par formulaire et le conteneur est Active Directory
http://support.microsoft.com/default.aspx?scid=kb;en-us;326340
[4] Documentation de FormsAuthentication
http://msdn2.microsoft.com/fr-fr/library/874sbx60(VS.71).aspx
[5] FAQ sur l'authentification par formulaire
http://support.microsoft.com/kb/910443/en-us
[6] Limites et taille d'un cookie:
http://support.microsoft.com/kb/306070/
[7] Un bon site pour acheter des éléments chimiques
http://www.smart-elements.com/index.php?arg=pse&PHPSESSID=c2e95757f2d58c25c0aded963022bd2b
Commentaires en attente de modération
Cet article a 1 réaction en attente de modération...
Laisser un commentaire
| « LA SAGA 7: authentification par formulaire, quelques scénarios | La SAGA 5: Utilisation des rôles » |