diff --git a/bin/load_gen b/bin/load_gen
index aa858490e194c7a1af8834a882d31289f617d530..a5b33cf11b66c6a0917c9578c29e2bfbefe1a633 100755
--- a/bin/load_gen
+++ b/bin/load_gen
@@ -61,7 +61,7 @@ var argv = require('optimist')
 .string('s')
 .describe('s', 'base URL to browserid server')
 .check(function(argv) {
-  return (typeof argv.s === 'string' || argv.l) != undefined;
+  return (argv.h || typeof argv.s === 'string' || argv.l) != undefined;
 })
 .alias('v', 'verifier')
 .describe('v', 'base URL to verifier service (default is browserid server + \'/verify\')')
diff --git a/lib/load_gen/common.js b/lib/load_gen/common.js
index 0a7cf61789c473e022578ff4195436de7309cd06..cd94b5ade023e161b63719957271f56ef26bdde2 100644
--- a/lib/load_gen/common.js
+++ b/lib/load_gen/common.js
@@ -67,7 +67,7 @@ exports.genAssertionAndVerify = function(cfg, user, ctx, email, audience, cb) {
     try {
       if (!typeof JSON.parse(r.body) === 'object') throw 'bogus response';
     } catch(e) {
-      return cb(e.toString() + " - " + r.body);
+      return cb(e.toString() + (r ? (" - " + r.body) : ""));
     }
 
     var assertion = crypto.getAssertion({
diff --git a/lib/wsapi.js b/lib/wsapi.js
index 4fb4bab3493eb30dc4f5d25b188d12a7b4f2a1dd..45ca5854e9a98398dfdaf1086ee5929f82a5bdbd 100644
--- a/lib/wsapi.js
+++ b/lib/wsapi.js
@@ -32,8 +32,16 @@ function clearAuthenticatedUser(session) {
   session.reset(['csrf']);
 }
 
-function isAuthed(req) {
-  return (req.session) ? req.session.userid : undefined;
+function isAuthed(req, requiredLevel) {
+  if (req.session && req.session.userid && req.session.auth_level) {
+    // 'password' authentication allows access to all apis.
+    // 'assertion' authentication, grants access to only those apis
+    // that don't require 'password'
+    if (requiredLevel === 'assertion' || req.session.auth_level === 'password') {
+      return true;
+    }
+  }
+  return false;
 }
 
 function bcryptPassword(password, cb) {
@@ -45,8 +53,12 @@ function bcryptPassword(password, cb) {
   });
 };
 
-function setAuthenticatedUser(session, uid) {
+function authenticateSession(session, uid, level) {
+  if (['assertion', 'password'].indexOf(level) === -1)
+    throw "invalid authentication level: " + level;
+
   session.userid = uid;
+  session.auth_level = level;
 }
 
 function checkPassword(pass) {
@@ -59,7 +71,7 @@ function checkPassword(pass) {
 exports.clearAuthenticatedUser = clearAuthenticatedUser;
 exports.isAuthed = isAuthed;
 exports.bcryptPassword = bcryptPassword;
-exports.setAuthenticatedUser = setAuthenticatedUser;
+exports.authenticateSession = authenticateSession;
 exports.checkPassword = checkPassword;
 exports.fowardWritesTo = undefined;
 
@@ -278,7 +290,7 @@ exports.setup = function(options, app) {
       // above
 
       // does the request require authentication?
-      if (wsapis[operation].authed && !isAuthed(req)) {
+      if (wsapis[operation].authed && !isAuthed(req, wsapis[operation].authed)) {
         return httputils.badRequest(resp, "requires authentication");
       }
 
diff --git a/lib/wsapi/account_cancel.js b/lib/wsapi/account_cancel.js
index d15da66b08a9cc3ab4d968df6a54fb599690f6ed..c0b59193ec497eab8f890f55636587df3b406b29 100644
--- a/lib/wsapi/account_cancel.js
+++ b/lib/wsapi/account_cancel.js
@@ -5,7 +5,7 @@ logger = require('../logging.js').logger;
 
 exports.method = 'post';
 exports.writes_db = true;
-exports.authed = true;
+exports.authed = 'assertion';
 
 exports.process = function(req, res) {
   db.cancelAccount(req.session.userid, function(error) {
diff --git a/lib/wsapi/add_email_with_assertion.js b/lib/wsapi/add_email_with_assertion.js
index 0fdb7bd9c35b7dc2d66fd3f615c3d71dec8984a8..1e6c6f99a19616d37fdebc50a015fb93d2d966e8 100644
--- a/lib/wsapi/add_email_with_assertion.js
+++ b/lib/wsapi/add_email_with_assertion.js
@@ -10,7 +10,7 @@ https = require('https');
 
 exports.method = 'post';
 exports.writes_db = true;
-exports.authed = true;
+exports.authed = 'assertion';
 exports.args = ['assertion'];
 
 // This WSAPI will be invoked when a user attempts to add a primary
diff --git a/lib/wsapi/auth_with_assertion.js b/lib/wsapi/auth_with_assertion.js
index 8db03f64bf3560b495df25dab8b548d9da0283ff..2e9c10f61877923682acf5b13ddb9adadc5255ed 100644
--- a/lib/wsapi/auth_with_assertion.js
+++ b/lib/wsapi/auth_with_assertion.js
@@ -33,7 +33,7 @@ exports.process = function(req, res) {
       if (type === 'primary') {
         return db.emailToUID(email, function(uid) {
           if (!uid) return res.json({ success: false, reason: "internal error" });
-          wsapi.setAuthenticatedUser(req.session, uid);
+          wsapi.authenticateSession(req.session, uid, 'assertion');
           return res.json({ success: true });
         });
       }
@@ -82,7 +82,7 @@ exports.process = function(req, res) {
           }
 
           logger.info("successfully created primary acct for " + email + " (" + r.userid + ")");
-          wsapi.setAuthenticatedUser(req.session, r.userid);
+          wsapi.authenticateSession(req.session, r.userid, 'assertion');
           res.json({ success: true });
         });
       }).on('error', function(e) {
diff --git a/lib/wsapi/authenticate_user.js b/lib/wsapi/authenticate_user.js
index f6f415c9fa5e31b90d63d072517f6b41b3487519..25b073bc60ef7eeb4da8a31e3285f36eb69104c1 100644
--- a/lib/wsapi/authenticate_user.js
+++ b/lib/wsapi/authenticate_user.js
@@ -50,7 +50,7 @@ exports.process = function(req, res) {
         } else {
           if (!req.session) req.session = {};
 
-          wsapi.setAuthenticatedUser(req.session, uid);
+          wsapi.authenticateSession(req.session, uid, 'password');
           res.json({ success: true });
 
 
diff --git a/lib/wsapi/cert_key.js b/lib/wsapi/cert_key.js
index c5952ba40c8e5e34d96a023698715be09f994ac1..69321169b74ff449e25c564cf32359fadbc224ce 100644
--- a/lib/wsapi/cert_key.js
+++ b/lib/wsapi/cert_key.js
@@ -7,7 +7,7 @@ config = require('../configuration.js');
 
 exports.method = 'post';
 exports.writes_db = false;
-exports.authed = true;
+exports.authed = 'password';
 exports.args = ['email','pubkey'];
 
 exports.process = function(req, res) {
diff --git a/lib/wsapi/complete_email_addition.js b/lib/wsapi/complete_email_addition.js
index deedcd4b90e6194ffc95530c108ee818f36fa944..c0449c5bfcdf83d2cbdabc3e8ecb2f9f4e7842c9 100644
--- a/lib/wsapi/complete_email_addition.js
+++ b/lib/wsapi/complete_email_addition.js
@@ -45,7 +45,13 @@ exports.process = function(req, res) {
               return res.json({ success: false });
             }
             db.updatePassword(uid, hash, function(err) {
-              if (err) logger.warn("couldn't update password during email verification: " + err);
+              if (err) {
+                logger.warn("couldn't update password during email verification: " + err);
+              } else {
+                // XXX: what if our software 503s?  User doens't get a password set and
+                // cannot change it.
+                wsapi.authenticateSession(req.session, uid, 'password');
+              }
               res.json({ success: !err });
             });
           });
diff --git a/lib/wsapi/complete_user_creation.js b/lib/wsapi/complete_user_creation.js
index 912e942629d8ed5876f123d259634c9d542b37a9..fd582b7ea02f0e9ff56aa0214cd02acbc823eb28 100644
--- a/lib/wsapi/complete_user_creation.js
+++ b/lib/wsapi/complete_user_creation.js
@@ -45,7 +45,7 @@ exports.process = function(req, res) {
           // FIXME: not sure if we want to do this (ba)
           // at this point the user has set a password associated with an email address
           // that they've verified.  We create an authenticated session.
-          wsapi.setAuthenticatedUser(req.session, uid);
+          wsapi.authenticateSession(req.session, uid, 'password');
           res.json({ success: true });
         }
       });
diff --git a/lib/wsapi/email_addition_status.js b/lib/wsapi/email_addition_status.js
index aa787fbb33b8f1aef3149ffdacc645aadaf69c4f..0139e779a137a591c3ff598eabd2add04211c5a9 100644
--- a/lib/wsapi/email_addition_status.js
+++ b/lib/wsapi/email_addition_status.js
@@ -9,7 +9,7 @@ db = require('../db.js');
 
 exports.method = 'get';
 exports.writes_db = false;
-exports.authed = true;
+exports.authed = 'assertion';
 exports.args = ['email'];
 
 exports.process = function(req, res) {
diff --git a/lib/wsapi/list_emails.js b/lib/wsapi/list_emails.js
index fb991940d3adb1a496f8b024f226ab49f093aa8a..4314bdf000345f80b61ebc72fbe34a45b2e0fa15 100644
--- a/lib/wsapi/list_emails.js
+++ b/lib/wsapi/list_emails.js
@@ -11,7 +11,7 @@ logger = require('../logging.js').logger;
 
 exports.method = 'get';
 exports.writes_db = false;
-exports.authed = true;
+exports.authed = 'assertion';
 
 exports.process = function(req, resp) {
   logger.debug('listing emails for user ' + req.session.userid);
diff --git a/lib/wsapi/logout.js b/lib/wsapi/logout.js
index ff48f014b33cf94d5685c6dac619f8b5ed878ad9..41a84b2a0529d1053abbdc4aa6b4e7a4d4d695c9 100644
--- a/lib/wsapi/logout.js
+++ b/lib/wsapi/logout.js
@@ -3,7 +3,7 @@ wsapi = require('../wsapi.js');
 
 exports.method = 'post';
 exports.writes_db = false;
-exports.authed = true;
+exports.authed = 'assertion';
 
 exports.process = function(req, res) {
   wsapi.clearAuthenticatedUser(req.session);
diff --git a/lib/wsapi/remove_email.js b/lib/wsapi/remove_email.js
index a98cbcda28d249ed79a7080854bd5734d533d4c6..9df5a3818836a9527e8e96ae2816de82f0078718 100644
--- a/lib/wsapi/remove_email.js
+++ b/lib/wsapi/remove_email.js
@@ -5,7 +5,7 @@ logger = require('../logging.js').logger;
 
 exports.method = 'post';
 exports.writes_db = true;
-exports.authed = true;
+exports.authed = 'assertion';
 exports.args = ['email'];
 
 exports.process = function(req, res) {
diff --git a/lib/wsapi/session_context.js b/lib/wsapi/session_context.js
index 89386ed008fda43f5a4e26660508f6cf18a9cc31..bc57b6bfe5b14bd97a025750fdc7a953d7ac4aac 100644
--- a/lib/wsapi/session_context.js
+++ b/lib/wsapi/session_context.js
@@ -29,13 +29,15 @@ exports.process = function(req, res) {
     logger.debug("NEW csrf token created: " + req.session.csrf);
   }
 
-  var auth_status = false;
+  var auth_level = undefined;
+  var authenticated = false;
 
   function sendResponse() {
     res.json({
       csrf_token: req.session.csrf,
       server_time: (new Date()).getTime(),
-      authenticated: auth_status,
+      authenticated: authenticated,
+      auth_level: auth_level,
       domain_key_creation_time: domainKeyCreationDate.getTime(),
       random_seed: crypto.randomBytes(32).toString('base64')
     });
@@ -43,7 +45,7 @@ exports.process = function(req, res) {
 
   // if they're authenticated for an email address that we don't know about,
   // then we should purge the stored cookie
-  if (!wsapi.isAuthed(req)) {
+  if (!wsapi.isAuthed(req, 'assertion')) {
     logger.debug("user is not authenticated");
     sendResponse();
   } else {
@@ -53,7 +55,8 @@ exports.process = function(req, res) {
         wsapi.clearAuthenticatedUser(req.session);
       } else {
         logger.debug("user is authenticated");
-        auth_status = true;
+        auth_level = req.session.auth_level;
+        authenticated = true;
       }
       sendResponse();
     });
diff --git a/lib/wsapi/stage_email.js b/lib/wsapi/stage_email.js
index e1c7235a5697ce3a59aab4ce3dd689745af16249..81d2f2c3e27dcb64c049db58b097f3c3ff872135 100644
--- a/lib/wsapi/stage_email.js
+++ b/lib/wsapi/stage_email.js
@@ -13,7 +13,7 @@ email = require('../email.js');
 
 exports.method = 'post';
 exports.writes_db = true;
-exports.authed = true;
+exports.authed = 'assertion';
 exports.args = ['email','site'];
 
 exports.process = function(req, res) {
diff --git a/lib/wsapi/update_password.js b/lib/wsapi/update_password.js
index fe647160e91e4c382be6ea23956115c4a9246a27..97dfb7c91aaaac643330146328ed12783ed0f875 100644
--- a/lib/wsapi/update_password.js
+++ b/lib/wsapi/update_password.js
@@ -7,7 +7,7 @@ bcrypt = require('../bcrypt');
 
 exports.method = 'post';
 exports.writes_db = true;
-exports.authed = true;
+exports.authed = 'password';
 exports.args = ['oldpass','newpass'];
 
 exports.process = function(req, res) {
diff --git a/lib/wsapi/user_creation_status.js b/lib/wsapi/user_creation_status.js
index c06b6f624885d87142c595b88159b68a649a38ba..f62e5ea9f3bceb347a18fe98dabffc0024805112 100644
--- a/lib/wsapi/user_creation_status.js
+++ b/lib/wsapi/user_creation_status.js
@@ -11,7 +11,7 @@ exports.process = function(req, res) {
   var email = req.query.email;
 
   // if the user is authenticated as the user in question, we're done
-  if (wsapi.isAuthed(req)) {
+  if (wsapi.isAuthed(req, 'assertion')) {
     db.userOwnsEmail(req.session.userid, email, function(owned) {
       if (owned) res.json({ status: 'complete' });
       else notAuthed();
diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js
index e2ab7fa071d129057a11d36e89218676254a93c3..a730fcad8fbcf71c4286374613892ea51c204506 100644
--- a/resources/static/include_js/include.js
+++ b/resources/static/include_js/include.js
@@ -817,14 +817,11 @@
             }
           };
         }
-      };
+      }
     } else {
       return {
         open: function(url, winopts, arg, cb) {
           setTimeout(function() { cb("unsupported browser"); }, 0);
-        },
-        onOpen: function(cb) {
-          setTimeout(function() { cb("unsupported browser"); }, 0);
         }
       };
     }
diff --git a/tests/primary-then-secondary-test.js b/tests/primary-then-secondary-test.js
index 55ef5df97f0e28d75337d46cd77ea3133e87d0ed..d4d244d5748ca79b583724cc953d4cb50b73e279 100755
--- a/tests/primary-then-secondary-test.js
+++ b/tests/primary-then-secondary-test.js
@@ -94,6 +94,17 @@ suite.addBatch({
   }
 });
 
+// now we have an account, and we're authenticated with an assertion.
+// check auth_level with session_context
+suite.addBatch({
+  "auth_level": {
+    topic: wsapi.get('/wsapi/session_context'),
+    "is 'assertion' after authenticating with assertion" : function(r, err) {
+      assert.strictEqual(JSON.parse(r.body).auth_level, 'assertion');
+    }
+  }
+});
+
 var token;
 
 // now we have a new account.  let's add a secondary to it
@@ -150,6 +161,17 @@ suite.addBatch({
   }
 });
 
+// after adding a secondary and setting password, we're password auth'd
+suite.addBatch({
+  "auth_level": {
+    topic: wsapi.get('/wsapi/session_context'),
+    "is 'password' after authenticating with password" : function(r, err) {
+      assert.strictEqual(JSON.parse(r.body).auth_level, 'password');
+    }
+  }
+});
+
+
 // adding a second secondary will not let us set the password
 suite.addBatch({
   "add a new email address to our account": {
diff --git a/tests/registration-status-wsapi-test.js b/tests/registration-status-wsapi-test.js
index dfb962282f18bee85ed605632593d75669778c51..73012662d29ddbb71955f097c045ca8592fdb5f1 100755
--- a/tests/registration-status-wsapi-test.js
+++ b/tests/registration-status-wsapi-test.js
@@ -175,7 +175,7 @@ suite.addBatch({
     topic: wsapi.get("/wsapi/session_context"),
     "we're authenticated": function (r, err) {
       assert.strictEqual(r.code, 200);
-      assert.strictEqual(JSON.parse(r.body).authenticated, true);
+      assert.strictEqual(JSON.parse(r.body).auth_level, 'password');
     },
     "but we can easily clear cookies on the client to change that!": function(r, err) {
       wsapi.clearCookies();
diff --git a/tests/two-level-auth-test.js b/tests/two-level-auth-test.js
new file mode 100755
index 0000000000000000000000000000000000000000..b486a42c424a876e7b3d194291efb556f0f18dad
--- /dev/null
+++ b/tests/two-level-auth-test.js
@@ -0,0 +1,115 @@
+#!/usr/bin/env node
+
+/* ***** 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 ***** */
+
+require('./lib/test_env.js');
+
+const
+assert = require('assert'),
+vows = require('vows'),
+start_stop = require('./lib/start-stop.js'),
+wsapi = require('./lib/wsapi.js'),
+primary = require('./lib/primary.js');
+
+var suite = vows.describe('primary-then-secondary');
+
+start_stop.addStartupBatches(suite);
+
+// this test verifies that a user who has only authenticated with
+// an assertion from their primary, may not call restricted apis
+
+const TEST_DOMAIN = 'example.domain',
+      TEST_EMAIL = 'testuser2@' + TEST_DOMAIN,
+      TEST_ORIGIN = 'http://127.0.0.1:10002';
+
+var primaryUser = new primary({
+  email: TEST_EMAIL,
+  domain: TEST_DOMAIN
+});
+
+// now let's generate an assertion using this user
+suite.addBatch({
+  "generating an assertion": {
+    topic: function() {
+      return primaryUser.getAssertion(TEST_ORIGIN);
+    },
+    "succeeds": function(r, err) {
+      assert.isString(r);
+    },
+    "and logging in with the assertion": {
+      topic: function(assertion)  {
+        wsapi.post('/wsapi/auth_with_assertion', {
+          email: TEST_EMAIL,
+          assertion: assertion
+        }).call(this);
+      },
+      "succeeds": function(r, err) {
+        var resp = JSON.parse(r.body);
+        assert.isObject(resp);
+        assert.isTrue(resp.success);
+      }
+    }
+  }
+});
+
+suite.addBatch({
+  "updating our password": {
+    topic: wsapi.post('/wsapi/update_password', { oldpass: '', newpass: 'frobaztastic' }),
+    "won't work": function(r) {
+      assert.strictEqual(r.code, 400);
+    }
+  },
+  "certifying a key": {
+    topic: wsapi.post('/wsapi/cert_key', { email: TEST_EMAIL, pubkey: 'fake_key' }),
+    "won't work": function(r) {
+      assert.strictEqual(r.code, 400);
+    }
+  },
+  "listing emails": {
+    topic: wsapi.get('/wsapi/list_emails'),
+    "works fine": function(r) {
+      assert.strictEqual(r.code, 200);
+      assert.equal(Object.keys(JSON.parse(r.body)).length, 1);
+    }
+  }
+});
+
+// shut the server down and cleanup
+start_stop.addShutdownBatches(suite);
+
+// run or export the suite.
+if (process.argv[1] === __filename) suite.run();
+else suite.export(module);