diff --git a/browserid/static/dialog/qunit.html b/browserid/static/dialog/qunit.html
index edbdabfc8df168368d3057f16a8fbe0f4800ba05..a22393863716be5e9f6b976a45a791fbd411f96e 100644
--- a/browserid/static/dialog/qunit.html
+++ b/browserid/static/dialog/qunit.html
@@ -1,6 +1,6 @@
 <html>
 	<head>
-		<link rel="stylesheet" type="text/css" href="../../../../../../../funcunit/qunit/qunit.css" />
+		<link rel="stylesheet" type="text/css" href="/funcunit/qunit/qunit.css" />
 		<title>dialog QUnit Test</title>
 		<script type='text/javascript'>
 			steal = {ignoreControllers: true}
diff --git a/browserid/static/dialog/resources/browserid-identities.js b/browserid/static/dialog/resources/browserid-identities.js
index a2c0c82e9b4ff10175b8fcbbf73a1c8adaab0ea3..09eab53895fe61455e1526866b8716faf0665b73 100644
--- a/browserid/static/dialog/resources/browserid-identities.js
+++ b/browserid/static/dialog/resources/browserid-identities.js
@@ -35,43 +35,107 @@
  *
  * ***** END LICENSE BLOCK ***** */
 "use strict";
-var BrowserIDIdentities = {
-  
-  syncIdentities: function(onSuccess, onFailure, onKeySyncFailure) {
-    // send up all email/pubkey pairs to the server, it will response with a
-    // list of emails that need new keys.  This may include emails in the
-    // sent list, and also may include identities registered on other devices.
-    // we'll go through the list and generate new keypairs
-    
-    // identities that don't have an issuer are primary authentications,
-    // and we don't need to worry about rekeying them.
-    var emails = getEmails();
-    var issued_identities = {};
-    _(emails).each(function(email_obj, email_address) {
+var BrowserIDIdentities = (function() {
+  function getIssuedIdentities() {
+      var emails = getEmails();
+      var issued_identities = {};
+      _(emails).each(function(email_obj, email_address) {
         issued_identities[email_address] = email_obj.pub;
       });
-    
-    var self = this;
-    BrowserIDNetwork.syncEmails(issued_identities, 
-      function onKeySyncSuccess(email, keypair) {
-        self.persistAddressAndKeyPair(email, keypair, "browserid.org:443");
-      },
-      onKeySyncFailure, onSuccess, onFailure);
-  },
-
-  persistAddressAndKeyPair: function(email, keypair, issuer) {
-    var new_email_obj= {
-      created: new Date(),
-      pub: keypair.pub,
-      priv: keypair.priv
-    };
-
-    if (issuer) {
-      new_email_obj.issuer = issuer;
+
+      return issued_identities;
+  }
+
+  function removeUnknownIdentities(unknown_emails) {
+    // first remove idenitites that the server doesn't know about
+    if (unknown_emails) {
+      _(unknown_emails).each(function(email_address) {
+        removeEmail(email_address);
+      });
+    }
+  }
+
+  var Identities = {
+    syncIdentities: function(onSuccess, onFailure, onKeySyncFailure) {
+      var issued_identities = getIssuedIdentities();
+
+      // send up all email/pubkey pairs to the server, it will response with a
+      // list of emails that need new keys.  This may include emails in the
+      // sent list, and also may include identities registered on other devices.
+      // we'll go through the list and generate new keypairs
+      
+      // identities that don't have an issuer are primary authentications,
+      // and we don't need to worry about rekeying them.
+
+      var self = this;
+      BrowserIDNetwork.syncEmails(issued_identities, function(resp) {
+        removeUnknownIdentities(resp.unknown_emails);
+
+        // now let's begin iteratively re-keying the emails mentioned in the server provided list
+        var emailsToAdd = resp.key_refresh;
+        
+        function addNextEmail() {
+          if (!emailsToAdd || !emailsToAdd.length) {
+            onSuccess();
+            return;
+          }
+
+          // pop the first email from the list
+          var email = emailsToAdd.shift();
+          var keypair = CryptoStubs.genKeyPair();
+
+          BrowserIDNetwork.setKey(email, keypair, function() {
+            // update emails list and commit to local storage, then go do the next email
+            self.addIdentity(email, keypair, "browserid.org:443");
+            addNextEmail();
+          }, onKeySyncFailure);
+        }
+
+        addNextEmail();
+      }, onFailure);
+    },
+
+    /**
+     * Persist an address and key pair.
+     * @method addIdentity
+     * @param {string} email - Email address.
+     * @param {object} keypair - Keypair for email address
+     * @param {string} [issuer] - Issuer of keypair
+     */
+    addIdentity: function(email, keypair, issuer) {
+      var new_email_obj= {
+        created: new Date(),
+        pub: keypair.pub,
+        priv: keypair.priv
+      };
+
+      if (issuer) {
+        new_email_obj.issuer = issuer;
+      }
+      
+      addEmail(email, new_email_obj);
+    },
+
+    /**
+     * Remove an email address.
+     * @method removeIdentity
+     * @param {string} email - Email address to remove.
+     */
+    removeIdentity: function(email) {
+      removeEmail(email);
+    },
+
+    /**
+     * Get the current list of stored identities.
+     * @method getIdentities
+     * @return {object} identities.
+     */
+    getIdentities: function() {
+      return getEmails();
     }
-    
-    addEmail(email, new_email_obj);
-  },
 
 
-};
+  };
+
+  return Identities;
+}());
diff --git a/browserid/static/dialog/resources/browserid-network.js b/browserid/static/dialog/resources/browserid-network.js
index 2c022898746566f86d81ce24c0a039b499e0aa6c..07d79c1e13f3b1be46e1dc89224369fe08c33a40 100644
--- a/browserid/static/dialog/resources/browserid-network.js
+++ b/browserid/static/dialog/resources/browserid-network.js
@@ -120,7 +120,14 @@ var BrowserIDNetwork = (function() {
       withCSRF(function() { 
         $.post("/wsapi/logout", {
           csrf: csrf_token
-        }, onSuccess );
+        }, function() {
+          csrf_token = undefined;
+          withCSRF(function() {
+            if(onSuccess) {
+              onSuccess();
+            }
+          });
+        } );
       });
     },
 
@@ -282,8 +289,11 @@ var BrowserIDNetwork = (function() {
     /**
      * Sync emails
      * @method syncEmails
+     * @param {object} issued_identities - Identities to check against.
+     * @param {function} [onSuccess] - Called with response when complete.
+     * @param {function} [onFailure] - Called on XHR failure.
      */
-    syncEmails: function(issued_identities, onKeySyncSuccess, onKeySyncFailure, onSuccess, onFailure) {
+    syncEmails: function(issued_identities, onSuccess, onFailure) {
       withCSRF(function() { 
         $.ajax({
           type: "POST",
@@ -292,36 +302,7 @@ var BrowserIDNetwork = (function() {
             emails: issued_identities,
             csrf: csrf_token
           },
-          success: function(resp, textStatus, jqXHR) {
-            // first remove idenitites that the server doesn't know about
-            if (resp.unknown_emails) {
-              _(resp.unknown_emails).each(function(email_address) {
-                removeEmail(email_address);
-              });
-            }
-
-            // now let's begin iteratively re-keying the emails mentioned in the server provided list
-            var emailsToAdd = resp.key_refresh;
-            
-            function addNextEmail() {
-              if (!emailsToAdd || !emailsToAdd.length) {
-                onSuccess();
-                return;
-              }
-
-              // pop the first email from the list
-              var email = emailsToAdd.shift();
-              var keypair = CryptoStubs.genKeyPair();
-
-              BrowserIDNetwork.setKey(email, keypair, function() {
-                // update emails list and commit to local storage, then go do the next email
-                onKeySyncSuccess(email, keypair);
-                addNextEmail();
-              }, onKeySyncFailure);
-            }
-
-            addNextEmail();
-          },
+          success: onSuccess,
           error: onFailure
         });
       });
diff --git a/browserid/static/dialog/test/qunit/browserid-identities_test.js b/browserid/static/dialog/test/qunit/browserid-identities_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..69084f00aa9bc0304c8ca23e5bcb5518f080ea2f
--- /dev/null
+++ b/browserid/static/dialog/test/qunit/browserid-identities_test.js
@@ -0,0 +1,113 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserIDNetwork: true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+/**
+ * This test assumes for authentication that there is a user named 
+ * "testuser@testuser.com" with the password "testuser"
+ */
+steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/browserid-identities", function() {
+  module("browserid-identities");
+
+  test("getIdentities", function() {
+    var identities = BrowserIDIdentities.getIdentities();
+    equal("object", typeof identities, "we have some identities");
+  });
+
+  test("addIdentity", function() {
+    BrowserIDIdentities.addIdentity("testemail@testemail.com", {
+      pub: "pub",
+      priv: "priv"
+    }, "issuer");
+
+    var identities = BrowserIDIdentities.getIdentities();
+    ok("testemail@testemail.com" in identities, "Our new email is added");
+  });
+
+  test("removeIdentity", function() {
+    BrowserIDIdentities.addIdentity("testemail@testemail.com", {
+      pub: "pub",
+      priv: "priv"
+    }, "issuer");
+
+    BrowserIDIdentities.removeIdentity("testemail@testemail.com");
+
+    var identities = BrowserIDIdentities.getIdentities();
+    equal(false, "testemail@testemail.com" in identities, "Our new email is removed");
+  });
+  
+  test("syncIdentities with no identities", function() {
+    clearEmails();
+    BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+      BrowserIDIdentities.syncIdentities(function onSuccess() {
+        ok(true, "we have synced identities");
+        start();
+      }, function onFailure() {
+        ok(false, "identity sync failure");
+        start();
+      }, function onKeySyncFailure() {
+        ok(false, "identity key sync failure");
+        start();
+      });
+
+    }, function() {
+      ok(false, "Authentication failure");
+      start();
+    });
+    stop();
+  });
+
+  test("syncIdentities with identities preloaded", function() {
+    BrowserIDNetwork.authenticate("testuser@testuser.com", "testuser", function() {
+      BrowserIDIdentities.syncIdentities(function onSuccess() {
+        ok(true, "we have synced identities");
+        start();
+      }, function onFailure() {
+        ok(false, "identity sync failure");
+        start();
+      }, function onKeySyncFailure() {
+        ok(false, "identity key sync failure");
+        start();
+      });
+
+    }, function() {
+      ok(false, "Authentication failure");
+      start();
+    });
+    stop();
+  });
+
+
+});
diff --git a/browserid/static/dialog/test/qunit/browserid-network_test.js b/browserid/static/dialog/test/qunit/browserid-network_test.js
index c14b14f84ddc7c173f3a0c33f94aeb2dd505990e..2ce1d4e659b68a13fb9e909f06b7c5ed7e3a53f8 100644
--- a/browserid/static/dialog/test/qunit/browserid-network_test.js
+++ b/browserid/static/dialog/test/qunit/browserid-network_test.js
@@ -1,3 +1,39 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserIDNetwork: true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
 /**
  * This test assumes for authentication that there is a user named 
  * "testuser@testuser.com" with the password "testuser"
diff --git a/browserid/static/dialog/test/qunit/qunit.js b/browserid/static/dialog/test/qunit/qunit.js
index 304452245c6cfa98a4c836d82dff4021b7b1a44d..05837c783c2853bdbfd3b1cdea71f4232ea0b9b8 100644
--- a/browserid/static/dialog/test/qunit/qunit.js
+++ b/browserid/static/dialog/test/qunit/qunit.js
@@ -1,3 +1,7 @@
-steal
+steal("/dialog/resources/storage.js",
+      "/dialog/resources/underscore-min.js",
+      "/dialog/resources/crypto-api.js",
+      "/dialog/resources/crypto.js")
   .plugins("funcunit/qunit")
-  .then("browserid-network_test");
+  .then("browserid-network_test")
+  .then("browserid-identities_test");