浏览器如何知道何时提示用户保存密码?

这与我在这里提出的问题有关: 如何让浏览器提示保存密码?

这就是问题所在: 我无法让浏览器提示我为正在开发的网站保存密码。(我说的是当你在 Firefox 上提交表单时,有时会出现一个条,上面写着“还记得 yoursite.com 的密码吗?是/不是现在/永远”)

这是非常令人沮丧的,因为 Firefox 的这个特性(以及大多数其他现代浏览器,我希望它们能以类似的方式工作)似乎是一个谜。这就像浏览器的一个魔术,它可以查看你的代码,或者你提交了什么,或者其他什么,如果它“看起来”像一个带有用户名(或电子邮件地址)字段和密码字段的登录表单,它会提供保存。

除了这种情况,在用户使用我的登录表单之后,它没有为他们提供这个选项,这让我很抓狂。:-)

(我检查了我的火狐设置——我没有告诉浏览器“从来没有”这个网站。它应该是提示。)

我的问题

Firefox 使用什么样的启发式方法来知道什么时候应该提示用户保存?这个问题应该不难回答,因为它就在 Mozilla 源代码中(我不知道在哪里查找,否则我会试着自己挖掘出来)。我也没有幸运地找到一篇博客文章或其他一些类似的开发人员注意到 Mozilla 开发人员关于这一点。

(对于 Safari 或 IE 浏览器来说,我可以接受这个问题的答案; 我可以想象所有的浏览器用户都遵循非常相似的规则,所以如果我能让它在其中一个浏览器中工作,它就能在其他浏览器中工作。)

(* 请注意,如果你给我的答案与 cookies、加密或其他任何关于我如何在本地数据库中存储密码的问题有关,那么你很有可能误解了我的问题。:-)

54295 次浏览

Well, on our site, a form field with name "username" type "text" immediately followed by a field with name "password" and type "password" seems to do the trick.

You should look at the Mozilla Password Manager Debugging page and the nsILoginManager docs for extension writers (just for the nitty gritty technical details of how Firefox deals with password management). You can dig into the answers there and other pages linked there to find out more than you probably ever wanted to know how the password manager interacts with sites and extensions.

(Specifically as pointed out in the password manager debugging doc, make sure you don't have autocomplete set to off in your html, as that will suppress the prompt to save the username and password)

Based off what I have read, I think Firefox detects passwords by form.elements[n].type == "password" (iterating through all form elements) and then detects the username field by searching backwards through form elements for the text field immediately before the password field (more info here). You might try something similar in Javascript and see if you can detect your password field.

From what I can tell, your login form needs to be part of a <form> or Firefox won't detect it. Setting id="password" on your password field probably couldn't hurt either.

If this is still giving you a lot of problems, I would recommend asking on one of the Mozilla project's developer mailing lists (you might even get a response from the developer who designed the feature).

I had the same problem and found a solution:

  1. to make the browser ask to store the password, user name and password boxes must be in a form and that form must be actually submitted. The submit button could return false from the onclick handler (so the submit does not actually happen).

  2. to make the browser restore the previously stored password, the input boxes have to exist in the main HTML form and not be created through javascript dynamically. The form can be created with display:none.

It's necessary to note, that the password is filled immediately upon the page is loaded and is present there during the whole session, so it can be read by injected javascript: it makes such attacks much worse. To avoid this, forwarding to a separate page just to log in is reasonable, and it solves all problems for which you started to read this topic :). As a partial solution I clear the fields upon submitting the form - if the user logs out and wants to log in again, the password is not filled by the browser, but that's minor to me.

Viliam


If you're using AJAX login, take a look at that source code: https://gist.github.com/968927

It consists of submitting a login form to a hidden iframe, so that IE and Chrome can detect the actual login, without having to reload the page.

The heuristics are pretty simple here: detect fields with certain names in certain order. Which ones, I can't tell, but this worked fine for me in Chrome and IE:

Username field: name "login", type "text";
Password field: name "password", type "password"
Submit button: element "input", type "submit".
Element "button" type "submit" did not work.

This seems to work for Firefox, Chrome and Safari on Mac. Not tested on Windows.

<form id="bridgeForm" action="#" target="loginframe" autocomplete="on">
<input type="text" name="username" id="username" />
<input type="password" name="password" id="password"/>
</form>


<iframe id="loginframe" name="loginframe" src="anyblankpage.html"></iframe>

This needs to be added to the page. It can't be added dynamically. The form and iframe can be set to display:none. If you don't set the src of the iframe the prompt won't show until you've submitted the form at least once.

Then call form submit():

bridgeForm.submit();

The action may be optional and the autocomplete may be optional. Haven't tested.

Note: On some browsers, the form has to be running on a server (not localhost and not the file system) before the browser will respond.

So this:

http://www.mysite.com/myPage.html

not this:

http://126.0.0.1/myPage.html
http://localhost/myPage.html
file://directory/myPage.html

I also noticed that Chrome will not offer to remember a password if the login form is still present after the login request, even if it's hidden on the page.

I guess it thinks the login action failed and thus refuse to store invalid credentials.

Seen on Chrome 34.0.

Works for me with angular, chrome, firefox: (I've searched and tested for hours - for chrome the form action parameter (#) was the answer. @1.21 gigawatts, thank you!!! Your answer was invaluable.)

form

firefox 30.0 - doesn't need a hidden iframe and submit button (as shown below), but needs the "login-form-autofill-fix" directive for recognizing the autofilled credendials, as follows:

<form name="loginForm" login-form-autofill-fix action="#" target="emptyPageForLogin" method="post" ng-submit="login({loginName:grpEmail,password:grpPassword})">
<input type="text" name=username" id="username" ng-model="grpEmail"/>
<input type="password" name="password" id="password" ng-model="grpPassword"/>
<button type="submit">Login</button>
</form>

hidden iframe

chrome 35.0 - doesn't need the above directive, but needs a hidden iframe and a submit button on the real form. The hidden iframe looks like

<iframe src="emptyPageForLogin.html" id="emptyPageForLogin" name="emptyPageForLogin" style="display:none"></iframe>

angular directive (using jqLite)

This works with angular 1.2.18

module.directive('loginFormAutofillFix', function() {
return function(scope, elem, attrs) {
if(!attrs.ngSubmit) {
return;
}
setTimeout(function() {
elem.unbind("submit").bind("submit", function(e) {
//DO NOT PREVENT!  e.preventDefault();
elem.find("input").triggerHandler("input");
scope.$apply(attrs.ngSubmit);
});
}, 0);
});

amendment

  • after some testing, I realised, that chrome needs a little timeout by the angular login method (200ms) - it seems, the redirect is sometimes just too fast for the password manager.
  • better clear browsercache... with every change

I'd recommend looking at the Firefox source code. It's actually pretty straightforward code.

The methods you want to look at are _onFormSubmit, _getFormFields and _getPasswordFields.

You might even find the problem you're having is infact an undiscovered bug in Firefox ;) https://bugzilla.mozilla.org/show_bug.cgi?id=1211780

The major keyword is here,

   <input type="password">

In addition to a lot that has been said already I realized that the input fields must not be "disabled". We had a multi-step login that first asks for the username and then, on the next screen, for the password. On that second screen we repeated the email but disabled it and that prevented Chrome et. al. from recognizing as a valid field for the username.

Since we really wanted to keep that disabled input field we ended up with this hacky workaround:

<input type="text" name="display-username" id="display-username" value="ENTERED_USERNAME" placeholder="Username" disabled="disabled">
<input type="text" name="username" id="username" value="ENTERED_USERNAME" placeholder="Username" style="display: none;">
<input type="password" name="password" id="password" value="" autofocus="autofocus" autocomplete="on" placeholder="Password" required="required">

I wouldn't go so far to recommend this but maybe it points somebody to something in their own code:

  1. The first element displays the username in a disabled input field. Disabled doesn't submit as part of the form and disabled isn't recognized by the browser.
  2. The second element is a proper input field, with type = "text" and name/id = "username". This is being recognized by the browser. In order to prevent the user from editing it we hide it with CSS (display:none).

If you have for example two type=text in your form before input type=password, Browser detect the nearest input type=text for your username.

This is not matter another input has is=username and name=username

for solving this problem you must put your username input that you want to save exactly before your input password

Good Luck :-)