It’s been a while since I last blogged about Firefox’s password manager. No big deal, it really hasn’t fundamentally changed since since I rewrote it 6 years ago for Firefox 3.0. Well, ok, the storage backend did switch to SQLite, but that’s mostly invisible to users. There’s a noteworthy change coming soon (see next post!), but I figured that would be easier to explain if I first talked about how things currently work. Which I’ve been meaning to do for a long time, ahem.
The first thing you should know is that there is no standard or specification for how login pages work! The browser isn’t involved with the login process, other than to do generic things like loading pages and running javascript. So we basically have to guess about what’s going on in order to fill or save a username/password, and sometimes sites can do things that break this guesswork. I’m actually surprised I don’t get questions about this more often.
So let’s talk about how two of the main functions work — filling in known logins, and saving new logins.
Filling in a known login
The overall process for filling in an existing stored login is simple and brutish.
- Use the chrome docloader service and nsIWebProgress to learn when we start loading a new page.
- Add a DOMContentLoaded event listener to learn when that page has mostly loaded.
- When that event fires…
- Check to see if there are any logins stored for this site. If not, we’re done.
- Loop through each form element on the page…
- Is there an <input type=password> in the form? If not, skip form.
- Check to see if any known logins match the particular conditions of this form. If not, skip form.
- Check to see if various other conditions prevent us from using the login in this form.
- Fill in the username and/or password. Great success!
Phew! But it’s the details of looking at a form where things get complex.
To start with, where do the username and password go? The password is fairly obvious, because we can look for the password-specific input type. (What if there’s more than one? Then we ignore everything after the first.) There’s no unique “username” type, instead we just look for the first regular input field before the password field. At least, that was before HTML5 added more input types. Now we also allow types that could be plausibly be usernames, like <input type=email> (but not types like <input type=color>). Note that this all relies on the order of fields in the DOM — we can’t detect cases where a username is intended to go after the password (thankfully I’ve never seen anyone do this), or cases where other text inputs are inserted between the actual username field and the password (perhaps with a table or CSS to adjust visual ordering).
And then there’s the quirks of the fields themselves. If your username is “alice”, what should happen if the username field already has “bob” filled in? (We don’t change it or fill the password.) Or, more common and depressing, what if the username field already contains “Enter your sign in name here”? In Mongolian? (We treat it like “bob”.) What if the page has <input maxlength=8> but your username is “billvonweiterheimerschmidt”? (We avoid filling anything the user couldn’t have typed.)
And then there’s the quirks of the saved logins. What if the username field already has “ALICE” instead of “alice”? (We ignore case when filling, it’s a little trickier when saving.) Is there a difference between <input name=user> and <input name=login>? (Nope, we stopped using the fieldname in Firefox 3 because it was being used inconsistently, even within a site.) What about a site has both a username+password _and_ a separate password-like PIN? (Surprisingly, we were able to make that work! Depending on the presence of a username field, we prefer one login or the other.)
And then. And then and then and then. Like I said, there’s no spec, and sometimes a site’s usage can break the guesses we make.
Saving a new/changed login
In comparison, the process for saving a login is simpler.
- Watch for any form submissions (via a chrome earlyformsubmit observer)
- Given a form submission, is there a password field in it? If not, we’re done.
- Determine the username and password from the form, and compare with existing logins…
- If username is new, ask if user wants to save this new login
- If username exists but the password is different, ask if user wants to change the saved password
- If username and password are already saved, there’s nothing else to do.
Of course, there are still a number of complicating details!
This whole process is initiated by a form submission. If a site doesn’t actually submit a form (via form.submit() or <button type=submit>), but just runs some JavaScript to process the login, the password manager won’t see it. And thus can’t offer to save a new/changed login for the user. (Note that this is easy for a site to work around — do your work in the form’s onsubmit, but return |false| to cancel the submission).
Oh, and there’s still the same question as before — how to determine which fields are the username and password? We reuse the same algorithm as when filling a page, for consistency. But there are a few wrinkles. The form might be changing a password, so there could be up to 3 relevant password fields (for the cases of: just a new password, old and new passwords, and old + new + confirm.). And the password fields could be in any order! (Surprisingly, again, this works.) The most common problem I’ve seen is an account signup page with other form fields between the username and password, such as “choose a user name, enter your shipping address, set a password”. The password manager will guess wrong and think your zipcode is your username.
Oh, and somewhere in all this I should mention how differences in URLs can prevent filling in a login (or result in saving a seemingly-duplicate login). Clearly google.com and yahoo.com logins should be separate. But we also match on protocol, so that a https://site.com login will not be filled in on http://site.com. And what about http://www.foo.com and foo.com or accounts.foo.com? (We treat them separately.) What about mail.mozilla.com and people.mozilla.com? (Also separate.) What you might not realize is that we also use the form’s action URL (i.e., where the form is submitted to), ever since bug 360493. While this prevented sending your myspace.com password to evilhacker.com, it also breaks things when a site uses slightly different URLs in various login pages, or later changes where their login forms submit to.
All the gory details
This is one of the benefits of being Open Source. If you want to see alllll the gory details of how the Firefox password manager works, you can look at the source code. See http://mxr.mozilla.org/mozilla-central/source/toolkit/components/passwordmgr/. In particular, LoginManagerContent.jsm contains the code implementing the stuff discussed in this post, with the main entry points being onContentLoaded() and onFormSubmit().
Finally (!), I’ll mention that the Firefox password manager has some built in logging to help with debugging problems. If you find it not working in some particular case, you can enable the logging and it will often make the problem clear — or at least clearer to a developer when you file a bug!
The fact that we still need this messy heuristic behaviour with little to no UI work is why I still hold a grudge against BrowserID.
I let my hopes get up when I heard about the plans to modernize the password manager, specify a way for sites to reliably indicate their login protocol, integrate OpenID support, and potentially rescue HTTP authentication by providing a unified UI for logging back out which other browsers could adapt and adopt…
…and then I read “Never mind working toward a standardized, protocol-agnostic UI to bring web authentication out of the 90s like was done with the downloads UI. We’ve decided that, instead, we’re going to bikeshed OpenID+WebFinger with yet another web-based login UI that will eventually go browser-native as soon as we convince the devil to put on his skates… Don’t worry. We promise it’ll get done.”
(Which reminds me… I need to find time to join the mailing list, report that BrowserID’s UI is flat-out broken on my Firefox to the point that I couldn’t use it if I were willing.)
Firefox’s new download manager already showed that leading by example is possible. Even if everything else flopped, they could have at least given HTTP Authentication a new lease on life.
Always bugs me that it asks me to save, before I know if it was right!
(this text is tiny Firefox mobile)
Yeah, this stuff needs a lot of improvement. And yeah, Persona UI is terrible as well.
We need this stuff fixed, or authentication will keep on sucking, no matter what.
Firefox doesn’t fill in Outlook.com passwords, fix it.
> […] there is no standard or specification for how login pages work!
This has always puzzled me as a egregious omission. This is probably the single most common kind of interaction required by most websites and yet there’s no standard that defines it.
Even worse, not only is HTTP Auth unmaintained, it is outright broken except in Opera 12. It still assumes the encoding is ANSI and somehow breaks the input (Presto-based Opera is the only one getting this right, assuming at least UTF-8). Provides no way whatsoever of logging out after logged in, and there is a huge inertia from most browser makers to fix or even improve the looks of it.
Even basic things like making the dialog tab-modal instead of window modal or applying the same awesomebar pop-ups as used by other things have been either outright refused as unnecessary or met with incredible resistance and lack of interest.
And this is baffling because HTTP Auth could easily have the same interface, if not better, than the one intended for BrowserID, without having to use Javascript, and completely supported by any browser.
Supporting HTTP Auth would be the login equivalent of supporting Asm.js instead of things like Google’s NaCl. Grab an existing standard and make it better, so that new systems can have all the bling and existing systems also gain from it.
@José Pedro:
My best guess is that either they fell victim to the CADT mindset or they, whether consciously or not, saw more glory to be had in creating BrowserID than fixing up something old and familiar.