|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: Les subtilités des formulaires de login en JS |
| 4 | +author: feugy |
| 5 | +tags: [javascript, login, formulaire, chrome, firefox, jQuery] |
| 6 | +published: false |
| 7 | +--- |
| 8 | + |
| 9 | +Quoi de plus simple qu'un formulaire de login ? |
| 10 | +En HTML, certes, mais lorsque qu'on parle d'application riche en javascript, c'est une autre histoire. |
| 11 | + |
| 12 | +Si vous ne comprenez pas pourquoi le navigateur ne retient pas vos logins, ne propose pas le mot de passe associé, ou ne réagit pas à la touche entrée, lisez la suite ! |
| 13 | + |
| 14 | +Cet article est le résultat de **l'étude empirique du comportement des navigateurs**. |
| 15 | +Il est donc sujet à caution: tout dépends de votre navigateur et de sa version. |
| 16 | +J'espère simplement vous aider à comprendre et éviter les principaux chausse-trappes, sans avoir recours à l'installation d'un plugin :) |
| 17 | + |
| 18 | + |
| 19 | +## Mais ce n'est qu'un formulaire ! |
| 20 | + |
| 21 | +Hélas non... Les formulaires de login (un champs <tt>text</tt> + un champ <tt>password</tt>) sont détectés par le navigateur, qui va les traiter différemment des autres champs textuels. |
| 22 | + |
| 23 | +C'est là le premier problème : |
| 24 | +>**Le formulaire doit être présent dans le DOM au chargement de la page.** |
| 25 | + |
| 26 | +Oui, vous avez compris : si vous construisez votre formulaire directement en JS, ou si vous utilisez un template que vous accrochez une fois la page chargée, votre formulaire sera ignoré par certains navigateurs. |
| 27 | + |
| 28 | +L'astuce que j'utilise consiste à: |
| 29 | + |
| 30 | +* inclure le formulaire de login dans ma page index.html |
| 31 | +* le masquer avec un style CSS <tt>display:none</tt>. |
| 32 | +* le déplacer par la suite en javascript là où il doit apparaitre dans le rendu |
| 33 | + |
| 34 | +**index.html** |
| 35 | + |
| 36 | + <div id="loginStock" style="display:none"> |
| 37 | + <form id="formLogin"> |
| 38 | + <input type="text" name="username"/> |
| 39 | + <input type="password" name="password"/> |
| 40 | + </form> |
| 41 | + </div> |
| 42 | + |
| 43 | +**login.js** |
| 44 | + |
| 45 | + $('.loginContainer', this.element).append($('#loginStock > *')); |
| 46 | + |
| 47 | + |
| 48 | +## Dois-je mettre un champ de type <tt>submit</tt> ? |
| 49 | + |
| 50 | +S'il n'y a que les champs de type <tt>text</tt> et <tt>password</tt>, le navigateur ne retiendra pas le mot de passe. |
| 51 | + |
| 52 | +>**Le champ de type <tt>submit</tt> est indispensable.** |
| 53 | +
|
| 54 | +Même s'il est masqué (le bouton n'est pas encore bien skinnable, même en CSS 3), il doit être présent. |
| 55 | + |
| 56 | +**index.html** |
| 57 | + |
| 58 | + <div id="loginStock" style="display:none"> |
| 59 | + <form id="formLogin"> |
| 60 | + <input type="text" name="username"/> |
| 61 | + <input type="password" name="password"/> |
| 62 | + <input type="submit" style="display:none"/> |
| 63 | + </form> |
| 64 | + <a href="#" class="submit"></a> |
| 65 | + </div> |
| 66 | + |
| 67 | +**login.js** |
| 68 | + |
| 69 | + $('.loginContainer .submit').click(function(event){ |
| 70 | + // Déclenche la soumission du formulaire. |
| 71 | + $('#formLogin').submit(); |
| 72 | + // Annule la propagation du Click sur le lien, pas de la soumission du formulaire ! |
| 73 | + return false; |
| 74 | + }).button({label:'log in !'}); |
| 75 | + |
| 76 | + |
| 77 | +## Doit-on stopper la propagation de l'événement <tt>submit</tt> ? |
| 78 | + |
| 79 | +Le problème se pose si vous ne réalisez pas un "vrai" POST lorsque l'utilisateur déclenche la soumission du formulaire. |
| 80 | +Dans la majorité des applications riches, l'authentification se fait via une API, et donc un appel Ajax. |
| 81 | + |
| 82 | +On se branche alors sur l'événement submit, et on annule sa propagation. |
| 83 | + |
| 84 | +Seulement, en stoppant l'événement de soumission, le navigateur ne retient pas le login et le mot de passe. |
| 85 | + |
| 86 | +>**Pour que le navigateur mémorise le couple login/mot de passe, l'événement <tt>submit</tt> doit se propager.** |
| 87 | +
|
| 88 | +Cela implique: |
| 89 | + |
| 90 | +* que le formulaire pointe sur une véritable url, sinon, une vilaine 404 apparaitra dans votre console |
| 91 | +* que le résultat de la soumission soit routée dans une iframe (désolé pour les puristes :)), sans quoi, toute la page se rechargera |
| 92 | + |
| 93 | +Donc généralement, mon service web propose une API POST qui ne fait rien et renvoie une page vide, et j'utilise une iframe masquée. |
| 94 | + |
| 95 | +**index.html** |
| 96 | + |
| 97 | + <div id="loginStock" style="display:none" method="post" action="/api/login/noop" target="postFrame"> |
| 98 | + <form id="formLogin"> |
| 99 | + <input autofocus="autofocus" type="text" name="username"/> |
| 100 | + <input autocomplete="on" type="password" name="password"/> |
| 101 | + <input type="submit" style="display:none"/> |
| 102 | + </form> |
| 103 | + <a href="#" class="submit"></a> |
| 104 | + </div> |
| 105 | + <iframe name="postFrame" class="hidden"></iframe> |
| 106 | + |
| 107 | +**login.js** |
| 108 | + |
| 109 | + $('#formLogin').submit(function(event){ |
| 110 | + // Ici mon appel ajax, et surtout, ne pas renvoyer false ni invoquer event.stopPropagation(). |
| 111 | + }); |
| 112 | + |
| 113 | + |
| 114 | +### Une petite astuce : lorsque l'authentification échoue. |
| 115 | + |
| 116 | +A partir du moment où l'événement <tt>submit</tt> est propagé, et qu'une réponse HTTP est reçue, le navigateur conservera bien le mot de passe et le login. |
| 117 | + |
| 118 | +Mais nous pouvons tirer parti de ce comportement, lorsque l'authentification échoue, par exemple si l'utilisateur est inconnu, ou le mot de passe erroné. |
| 119 | + |
| 120 | +Ainsi, mon appel Ajax est toujours **synchrone** (parfois ça sert !), et s'il échoue, alors dans ce cas, j'annule la propagation de l'événement <tt>submit</tt> pour ne pas conserver les mauvais identifiants. |
| 121 | + |
| 122 | + |
| 123 | + |
| 124 | +## La touche entrée ne soumet pas toujours mon formulaire... |
| 125 | + |
| 126 | +Hélas oui, c'est un comportement étrange de certains navigateurs : un formulaire sera automatiquement soumit si on appuie sur entrée dans un de ses champs, sauf si le bouton submit est invisible ! |
| 127 | + |
| 128 | +Donc il faut eviter le <tt>display:none</tt> ou le <tt>visibility:hidden</tt> sur le champ. |
| 129 | +Personnellement, je lui affecte une taille de zéro. Vous pouvez aussi le positionner en absolu, en dehors de la zone visible, ou le mettre en dessous d'un autre élément avec le <tt>z-index</tt> |
| 130 | + |
| 131 | +**index.html** |
| 132 | + |
| 133 | + <div id="loginStock" style="display:none" method="post" action="/api/login/noop" target="postFrame"> |
| 134 | + <form id="formLogin"> |
| 135 | + <input autofocus="autofocus" type="text" name="username"/> |
| 136 | + <input autocomplete="on" type="password" name="password"/> |
| 137 | + <input type="submit" style="width:0; height:0; border:0; padding:0"/> |
| 138 | + </form> |
| 139 | + <a href="#" class="submit"></a> |
| 140 | + </div> |
| 141 | + |
| 142 | + |
| 143 | +## Conclusion |
| 144 | + |
| 145 | +Vous voyez ? quand je vous disais que ce n'était pas trivial... |
| 146 | + |
| 147 | +Mais maintenant, vous avez toutes les clefs pour faire des formulaires qui exploitent le cache de login/mot de passe des navigateurs. |
| 148 | + |
| 149 | +Espérons que dans un futur proche, les navigateurs harmonisent un peu plus leur fonctionnement, de manière à éviter tout ces tricks... |
| 150 | + |
| 151 | +Damien. |
0 commit comments