Using nsILoginManager

Working with the Login Manager

Extensions often need to securely store passwords to external sites, web applications, and so on. To do so securely, they can use nsILoginManager, which provides for secure storage of sensitive password information and nsILoginInfo, which provides a way of storing login information.

Getting nsILoginManager

To get a component implementing nsILoginManager, use the following:

var passwordManager = Components.classes["@mozilla.org/login-manager;1"].getService(
	Components.interfaces.nsILoginManager
);

Most Login Manager functions take an nsILoginInfo object as a parameter. An nsILoginInfo object contains the following attributes: hostname, form submit URL, HTTP realm, username, username field, password, and password field. The hostname, username and password attributes are mandatory, while the other fields are set based on whether the login is for a web page form or an HTTP/FTP authentication site login. See the nsILoginInfo attribute definitions for more details. Defining an nsILoginInfo object is simple:

var nsLoginInfo = new Components.Constructor(
	"@mozilla.org/login-manager/loginInfo;1",
	Components.interfaces.nsILoginInfo,
	"init"
);
	
var loginInfo = new nsLoginInfo(
	hostname, formSubmitURL, httprealm, username, password, usernameField, passwordField
);

Examples

Creating a login for a web page

var formLoginInfo = new nsLoginInfo(
	'http://www.example.com',
	'http://login.example.com',
	null,
	'joe',
	'SeCrEt123',
	'uname',
	'pword'
);

This login would correspond to a HTML form such as:

<form action="http://login.example.com/foo/authenticate.cgi">
	<div>Please log in.</div>
	<label>Username:</label> <input type="text" name="uname">
	<label>Password:</label> <input type="password" name="pword">
</form>

Creating a site authentication login

var authLoginInfo = new nsLoginInfo(
	'http://www.example.com',
	null,
	'ExampleCo Login',
	'alice',
	'SeCrEt321',
	"",
	""
);

This would correspond to a login on http://www.example.com when the server sends a reply such as:

 HTTP/1.0 401 Authorization Required
 Server: Apache/1.3.27
 WWW-Authenticate: Basic realm="ExampleCo Login"

Creating a local extension login

var extLoginInfo = new nsLoginInfo(
	'chrome://firefoo',
	null,
	'User Registration',
	'bob',
	'123sEcReT',
	"",
	""
);

From a component creating a new info block is done slightly differently:

var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
var extLoginInfo = new nsLoginInfo('chrome://firefoo', null, 'User Registration', 'bob', '123sEcReT', '', '');
//var extLoginInfo = new nsLoginInfo(aHostname, aFormSubmitURL, aHttpRealm, aUsername, aPassword, aUsernameField, aPasswordField)

The Login Manager treats this as if it was a web site login. You should use your extension's chrome:// URL to prevent conflicts with other extensions, and a realm string which briefly denotes the login's purpose. But there neither is there anything wrong if your Manager tells the user the full details: What is the password for? In which file is the password stored? Which keyring or system (Gnome, KDE-wallet, etc.) is being used, so the user actually knows what is going on and malware has a harder stand.

Storing a password

To store a password in the Login Manager, you first need to create an nsILoginInfo object as defined above. Then you simply need to call the nsILoginManager method addLogin().

myLoginManager.addLogin(loginInfo);

Note: This will throw an exception if both the httprealm and formSubmitURL parameters are NULL. One must be specified when storing a password. The hostname, username and password parameters are also mandatory.

Retrieving a password

Retrieving a password from the Login Manager is slightly more difficult. In order to locate a password, the hostname, formSubmitURL and httprealm must match exactly what is stored for the password to be found. The only exception is that if the stored formSubmitURL is blank, in which case the formSubmitURL parameter is ignored. Note that the hostname and formSubmitURL arguments should not include the path from the full URL. The example below should serve as a starting point for matching form logins:

var hostname = 'http://www.example.com';
var formSubmitURL = 'http://www.example.com';  // not http://www.example.com/foo/auth.cgi
var httprealm = null;
var username = 'user';
var password;

try {
	// Get Login Manager
	var myLoginManager = Components.classes["@mozilla.org/login-manager;1"].
		getService(Components.interfaces.nsILoginManager);
	
	// Find users for the given parameters
	var logins = myLoginManager.findLogins({}, hostname, formSubmitURL, httprealm);

	// Find user from returned array of nsILoginInfo objects
	for (var i = 0; i < logins.length; i++) {
		if (logins[i].username == username) {
			password = logins[i].password;
			break;
		}
	}
}

catch(ex) {
	// This will only happen if there is no nsILoginManager component class
}

Note that the user will be prompted for their master password if they have chosen to set one to secure their passwords.

Removing a password

Removing a password is simple:

myLoginManager.removeLogin(loginInfo);

When removing a password the specified nsILoginInfo object must exactly match what was stored or an exception will be thrown. This includes the password attribute. Here's an example on how to remove the password without actually knowing what the password is:

// example values
var hostname = 'http://www.example.com';
var formSubmitURL = 'http://www.example.com';
var httprealm = null;
var username = 'user';

try {
	// Get Login Manager
	var passwordManager = Components.classes["@mozilla.org/login-manager;1"].
		getService(Components.interfaces.nsILoginManager);

	// Find users for this extension
	var logins = passwordManager.findLogins({}, hostname, formSubmitURL, httprealm);

	for (var i = 0; i < logins.length; i++) {
		if (logins[i].username == username) {
			passwordManager.removeLogin(logins[i]);
			break;
		}
	}
}
catch(ex) {
	// This will only happen if there is no nsILoginManager component class
}

Changing stored login information

Changing a password is rather simple. Since all this does is make a removeLogin() call followed by an addLogin() call, it has the same caveats as both of them: namely that the oldLogin must match an existing login exactly (see above) and that the newLogin attributes must be set correctly.:

myLoginManager.modifyLogin(oldLogin, newLogin);

Login Manager notifications

Firefox 3.5 note

The Login Manager notifications were added in Firefox 3.5.

Firefox 3.5 and later send assorted notifications when various Login Manager related events occur, including when form autofill does not occur for various reasons, as well as when changes are made to the Login Manager's database. See the Login Manager section of the article on observer notifications for details.

Debugging

The login manager implementation has the ability to send debug messages to the Error Console, which can provide some visibility into what it's doing. To enable the debug logging, see http://wiki.mozilla.org/Firefox:Pass...ager_Debugging.

Supporting older versions of Gecko

If you want your extension to support both Gecko 1.9 (Firefox 3, Thunderbird 3, SeaMonkey 2) and older versions it will need to implement both the nsILoginManager and nsIPasswordManager components. A simple method to do this is as follows:

if ("@mozilla.org/passwordmanager;1" in Components.classes) {
	// Password Manager exists so this is not Firefox 3 (could be Firefox 2, Netscape, SeaMonkey, etc).
	// Password Manager code
} else if ("@mozilla.org/login-manager;1" in Components.classes) {
	// Login Manager exists so this is Firefox 3
	// Login Manager code
}

See also