diff --git a/bin/keysigner b/bin/keysigner
index b2002a722fdb8049d6137d7f3f3854e395a21a1b..316083aabf6a50f1ac3d309c5849f0f6d8cd3826 100755
--- a/bin/keysigner
+++ b/bin/keysigner
@@ -75,14 +75,13 @@ try {
   process.exit(1);
 }
 
-
 // and our single function
-app.post('/wsapi/cert_key', validate(["email", "pubkey"]), function(req, resp) {
+app.post('/wsapi/cert_key', validate(["email", "pubkey", "ephemeral"]), function(req, resp) {
   var startTime = new Date();
   cc.enqueue({
     pubkey: req.body.pubkey,
     email: req.body.email,
-    validityPeriod: config.get('certificate_validity_ms'),
+    validityPeriod: (req.body.ephemeral ? config.get('ephemeral_session_duration_ms') : config.get('certificate_validity_ms')),
     hostname: HOSTNAME
   }, function (err, r) {
     var reqTime = new Date - startTime;
diff --git a/example/rp/index.html b/example/rp/index.html
index d14fa1ad693c3b72b16dd057c7b6a1da0e5eaeb2..924e8f350f6dbe14787961e98a3a896fbc561259 100644
--- a/example/rp/index.html
+++ b/example/rp/index.html
@@ -15,10 +15,11 @@ BrowserID Relying Party
 body { margin: auto; font: 13px/1.5 Helvetica, Arial, 'Liberation Sans', FreeSans, sans-serif; }
 a:link, a:visited { font-style: italic; text-decoration: none; color: #008; }
 a:hover { border-bottom: 2px solid black ; }
-.title { font-size: 2em; font-weight: bold; text-align: center; margin: 1.5em; }
-.intro { font-size: 1.2em; width: 600px; margin: auto; }
-.specify { font-size: 1.1em; width: 600px; padding-top: 2em; margin: auto; }
-.assertion, .verifierResp { width: 600px; margin: auto; }
+.title { font-size: 2em; font-weight: bold; text-align: center; margin: 1.5em auto 1.5em auto; }
+.intro { font-size: 1.2em;  }
+.specify { font-size: 1.1em; padding-top: 2em; }
+body div { width: 600px; margin: auto; }
+
 pre {
   font-family: 'lucida console', monaco, 'andale mono', 'bitstream vera sans mono', consolas, monospace;
   border: 3px solid #666;
@@ -31,7 +32,6 @@ pre {
   background-color: #333;
 /*  white-space: pre;*/
   font-size: .9em;
-  width:600px;
   word-wrap: break-word;
 }
 
@@ -60,12 +60,6 @@ pre {
   <p>What flavor of assertion would you like?</p>
   <ul>
     <li>
-      <input type="checkbox" id="silent"> 
-      <label for="silent">Silent</label>
-    </li><li>
-      <input type="checkbox" id="allowPersistent">
-      <label for="allowPersistent">Allow persistent sign-in</label>
-    </li><li>
       <input type="checkbox" id="privacy">
       <label for="privacy">Supply a privacy policy</label>
     </li><li>
@@ -76,15 +70,26 @@ pre {
       <label for="requiredEmail">Require a specific email</label><br />
     </li>
   </ul>
-    <button>Get an assertion</button>
+    <button class="assertion">Get an assertion</button>
+    <button class="logout">logout</button>
+</div>
+
+<div class="loginEvents">
+  <h2>login events</h2>
+  <pre> ... login events ... </pre>
 </div>
 
-<div class="verifierResp">
-  <pre> ... verifier response ... </pre>
+<div class="logoutEvents">
+  <h2>logout events</h2>
+  <pre> ... logout events ... </pre>
 </div>
 
-<div class="assertion">
-  <pre> ... ye' ol' assertion ... </pre>
+<div class="loginCanceledEvents">
+  <h2>loginCanceled events</h2>
+  <pre> ... loginCanceled events ... </pre>
+</div>
+
+
 </body>
 
 </div>
@@ -92,6 +97,12 @@ pre {
 <script src="https://browserid.org/include.js"></script>
 <script>
 
+function loggit() {
+  try {
+    console.log.apply(console, arguments);
+  } catch(e) {}
+}
+
 // a function to check an assertion against the server
 function checkAssertion(assertion) {
   $.ajax({
@@ -103,37 +114,47 @@ function checkAssertion(assertion) {
       audience: window.location.protocol + "//" + window.location.host
     },
     success: function(data, textStatus, jqXHR) {
-      $(".verifierResp > pre").text(JSON.stringify(data, null, 4));
+      $(".loginEvents > pre").text(JSON.stringify(data, null, 4));
     },
     error: function(jqXHR, textStatus, errorThrown) {
       var resp = jqXHR.responseText ? JSON.parse(jqXHR.responseText) : errorThrown;
-      $(".verifierResp > pre").text(resp);
+      $(".loginEvents > pre").text(resp);
     }
   });
 };
 
+navigator.id.addEventListener('login', function(event) {
+  loggit("login event");
+  checkAssertion(event.assertion);
+});
+
+navigator.id.addEventListener('logout', function(event) {
+  loggit("logout event");
+  var txt = 'got event at ' + (new Date).toString();
+  $(".logoutEvents > pre").text(txt);
+});
+
+navigator.id.addEventListener('loginCanceled', function(event) {
+  loggit("loginCanceled");
+  var txt = 'got event at ' + (new Date).toString();
+  $(".loginCanceledEvents > pre").text(txt);
+});
+
 $(document).ready(function() {
-  $(".specify button").click(function() {
+  $(".specify button.assertion").click(function() {
     $("pre").text("... waiting ...");
 
     var requiredEmail = $.trim($('#requiredEmail').val());
     if (!requiredEmail.length) requiredEmail = undefined;
 
-    navigator.id.get(function(assertion) {
-      if (!assertion) {
-        $(".assertion pre").text("navigator.id.get() returns NULL");
-      } else {
-        $(".assertion pre").text(assertion);
-        checkAssertion(assertion);
-      }
-    }, {
-      silent: $('#silent').attr('checked'),
-      allowPersistent: $('#allowPersistent').attr('checked'),
+    navigator.id.request({
       privacyURL: $('#privacy').attr('checked') ? "/privacy.html" : undefined,
       tosURL: $('#tos').attr('checked') ? "/TOS.html" : undefined,
       requiredEmail: requiredEmail
     });
   });
+
+  $(".specify button.logout").click(navigator.id.logout);
 });
 
 </script>
diff --git a/lib/configuration.js b/lib/configuration.js
index eca775e276a164affb00a5fa666e676a9b37f779..8ba0fabc93cc20943e9df3bc9d10cda75c97c627 100644
--- a/lib/configuration.js
+++ b/lib/configuration.js
@@ -128,6 +128,10 @@ var conf = module.exports = convict({
     doc: "How long may a user stay signed?",
     format: 'integer = 1209600000'
   },
+  ephemeral_session_duration_ms: {
+    doc: "How long a user on a shared computer shall be authenticated",
+    format: 'integer = 3600000' // 1 hour
+  },
   certificate_validity_ms: {
     doc: "For how long shall certificates issued by BrowserID be valid?",
     format: 'integer = 86400000'
diff --git a/lib/db/mysql.js b/lib/db/mysql.js
index 2931097bcde0d45dd58fdeef5265e1c1524a2a6f..19a6aa4ff01375917223f96af897e89d60817de5 100644
--- a/lib/db/mysql.js
+++ b/lib/db/mysql.js
@@ -118,7 +118,6 @@ exports.open = function(cfg, cb) {
     logger.debug("connecting to database: " + database);
     options.database = database;
     client = mysql.createClient(options);
-
     client.ping(function(err) {
       logger.debug("connection to database " + (err ? ("fails: " + err) : "established"));
       cb(err);
diff --git a/lib/http_forward.js b/lib/http_forward.js
index cb4396a4b65c8281f5330fbfddd053229a9379c2..5277aa95643a31659a9a45085902e5b31cc37065 100644
--- a/lib/http_forward.js
+++ b/lib/http_forward.js
@@ -67,12 +67,14 @@ module.exports = function(dest, req, res, cb) {
 
   // forward header
   if (req.headers['accept-language']) {
-      preq.setHeader('Accept-Language', req.headers['accept-language']);
+    preq.setHeader('Accept-Language', req.headers['accept-language']);
   }
 
   // if the body has already been parsed, we'll write it
   if (req.body) {
-    var data = querystring.stringify(req.body);
+    var data;
+    if (req.headers['content-type'].indexOf('application/json') === 0) data = JSON.stringify(req.body);
+    else data = querystring.stringify(req.body);
     preq.setHeader('content-length', data.length);
     preq.write(data);
     preq.end();
diff --git a/lib/static_resources.js b/lib/static_resources.js
index 0b25b8948b82ec45b6f48118e0a4d9846386eb33..f5dc77d8c059b7e652922993d753d7eb99094917 100644
--- a/lib/static_resources.js
+++ b/lib/static_resources.js
@@ -95,6 +95,7 @@ var dialog_js = und.flatten([
     '/dialog/controllers/provision_primary_user.js',
     '/dialog/controllers/primary_user_provisioned.js',
     '/dialog/controllers/email_chosen.js',
+    '/dialog/controllers/is_this_your_computer.js',
 
     '/dialog/start.js'
   ]]);
diff --git a/lib/validate.js b/lib/validate.js
index ebe808dd35e85057f200f33cdc28900a716ffc45..b9f6d4dbc911ab54105afdff8f5719f5df430be4 100644
--- a/lib/validate.js
+++ b/lib/validate.js
@@ -27,7 +27,7 @@ module.exports = function (params) {
 
     try {
       params.forEach(function(k) {
-        if (!params_in_request || !params_in_request.hasOwnProperty(k) || typeof params_in_request[k] !== 'string') {
+        if (!params_in_request || !params_in_request.hasOwnProperty(k)) {
           throw k;
         }
       });
diff --git a/lib/wsapi.js b/lib/wsapi.js
index 7ee1f4a8d24e85b058a316fb657d2e3585831c6a..c76c7e3e23bf501492316c40eb4dcf1b3a4c4c0a 100644
--- a/lib/wsapi.js
+++ b/lib/wsapi.js
@@ -77,7 +77,7 @@ function bcryptPassword(password, cb) {
   });
 };
 
-function authenticateSession(session, uid, level) {
+function authenticateSession(session, uid, level, duration_ms) {
   if (['assertion', 'password'].indexOf(level) === -1)
     throw "invalid authentication level: " + level;
 
@@ -87,6 +87,9 @@ function authenticateSession(session, uid, level) {
       session.auth_level !== level) {
     logger.info("not resetting cookies to 'assertion' authenticate a user who is already password authenticated");
   } else {
+    if (duration_ms) {
+      session.setDuration(duration_ms);
+    }
     session.userid = uid;
     session.auth_level = level;
   }
diff --git a/lib/wsapi/auth_with_assertion.js b/lib/wsapi/auth_with_assertion.js
index 8781151358379e93e4f3e0bbe09c182c62b75667..7811124f8387bae3f6092b6b392d5cd34d1457c1 100644
--- a/lib/wsapi/auth_with_assertion.js
+++ b/lib/wsapi/auth_with_assertion.js
@@ -15,7 +15,7 @@ https = require('https');
 exports.method = 'post';
 exports.writes_db = false;
 exports.authed = false;
-exports.args = ['assertion'];
+exports.args = ['assertion', 'ephemeral'];
 exports.i18n = false;
 
 exports.process = function(req, res) {
@@ -41,8 +41,10 @@ exports.process = function(req, res) {
         return db.emailToUID(email, function(err, uid) {
           if (err) return wsapi.databaseDown(res, err);
           if (!uid) return res.json({ success: false, reason: "internal error" });
-          wsapi.authenticateSession(req.session, uid, 'assertion');
-          return res.json({ success: true });
+          wsapi.authenticateSession(req.session, uid, 'assertion',
+                                    req.body.ephemeral ? config.get('ephemeral_session_duration_ms')
+                                                       : config.get('authentication_duration_ms'));
+          return res.json({ success: true, userid: uid });
         });
       }
       else if (type === 'secondary') {
@@ -90,8 +92,10 @@ exports.process = function(req, res) {
           }
 
           logger.info("successfully created primary acct for " + email + " (" + r.userid + ")");
-          wsapi.authenticateSession(req.session, r.userid, 'assertion');
-          res.json({ success: true });
+          wsapi.authenticateSession(req.session, r.userid, 'assertion',
+                                    req.body.ephemeral ? config.get('ephemeral_session_duration_ms')
+                                                       : config.get('authentication_duration_ms'));
+          res.json({ success: true, userid: r.userid });
         });
       }).on('error', function(e) {
         logger.error("failed to create primary user with assertion for " + email + ": " + e);
diff --git a/lib/wsapi/authenticate_user.js b/lib/wsapi/authenticate_user.js
index b1715a1b4c21fce281502e366ab4c1b47b8877fd..89e524ec8a2cde0a5e90bdfb6f38d1534c29d544 100644
--- a/lib/wsapi/authenticate_user.js
+++ b/lib/wsapi/authenticate_user.js
@@ -16,7 +16,7 @@ statsd = require('../statsd');
 exports.method = 'post';
 exports.writes_db = false;
 exports.authed = false;
-exports.args = ['email','pass'];
+exports.args = ['email','pass', 'ephemeral'];
 exports.i18n = false;
 
 exports.process = function(req, res) {
@@ -59,8 +59,10 @@ exports.process = function(req, res) {
         } else {
           if (!req.session) req.session = {};
 
-          wsapi.authenticateSession(req.session, uid, 'password');
-          res.json({ success: true });
+          wsapi.authenticateSession(req.session, uid, 'password',
+                                    req.body.ephemeral ? config.get('ephemeral_session_duration_ms')
+                                                       : config.get('authentication_duration_ms'));
+          res.json({ success: true, userid: uid });
 
 
           // if the work factor has changed, update the hash here.  issue #204
diff --git a/lib/wsapi/cert_key.js b/lib/wsapi/cert_key.js
index 9b642eb341646f360b8e4288dd166ed15b6d6713..777d61223b6732ab620c66325baa9975eac891f5 100644
--- a/lib/wsapi/cert_key.js
+++ b/lib/wsapi/cert_key.js
@@ -14,7 +14,7 @@ wsapi = require('../wsapi.js');
 exports.method = 'post';
 exports.writes_db = false;
 exports.authed = 'password';
-exports.args = ['email','pubkey'];
+exports.args = ['email','pubkey','ephemeral'];
 exports.i18n = false;
 
 exports.process = function(req, res) {
diff --git a/lib/wsapi/complete_user_creation.js b/lib/wsapi/complete_user_creation.js
index 882351b630f784c34528302de0682ff9870859fc..dca109d14da9a856d1ab7fc6792ec100a932a37c 100644
--- a/lib/wsapi/complete_user_creation.js
+++ b/lib/wsapi/complete_user_creation.js
@@ -50,7 +50,8 @@ 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.authenticateSession(req.session, uid, 'password');
+          wsapi.authenticateSession(req.session, uid, 'password',
+                                    config.get('ephemeral_session_duration_ms'));
           res.json({ success: true });
         }
       });
diff --git a/lib/wsapi/prolong_session.js b/lib/wsapi/prolong_session.js
new file mode 100644
index 0000000000000000000000000000000000000000..2c9d5c02edb4ab2b9d04ee76ad519573f3c7debb
--- /dev/null
+++ b/lib/wsapi/prolong_session.js
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const
+config = require('../configuration.js'),
+wsapi = require('../wsapi.js');
+
+exports.method = 'post';
+exports.writes_db = false;
+exports.authed = 'assertion';
+exports.i18n = false;
+
+exports.process = function(req, res) {
+  wsapi.authenticateSession(req.session, req.session.userid, req.session.auth_level,
+                            config.get('authentication_duration_ms'));
+  res.send(200);
+};
diff --git a/lib/wsapi/session_context.js b/lib/wsapi/session_context.js
index 8b7f9e13d7a058f28192ae354cfe55b2a9e5b09b..c62ec790622de67d6681e8700db50db13308f8b5 100644
--- a/lib/wsapi/session_context.js
+++ b/lib/wsapi/session_context.js
@@ -50,6 +50,10 @@ exports.process = function(req, res) {
     if (config.get('enable_code_version')) {
       respObj.code_version = version();
     }
+    if (req.session && req.session.userid) {
+      respObj.userid = req.session.userid;
+    }
+
     res.json(respObj);
   };
 
diff --git a/lib/wsapi_client.js b/lib/wsapi_client.js
index a86095e8f5b6f5fdbeca736e7a98c9d6c9b8c4c9..256fcf71c6e395fad3a18dae23fadbb5cef2c0fc 100644
--- a/lib/wsapi_client.js
+++ b/lib/wsapi_client.js
@@ -42,6 +42,15 @@ exports.clearCookies = function(ctx) {
   if (ctx && ctx.session) delete ctx.session;
 };
 
+exports.getCookie = function(ctx, which) {
+  if (typeof which === 'string') which = new Regex('/^' + which + '$/');
+  var cookieNames = Object.keys(ctx.cookieJar);
+  for (var i = 0; i < cookieNames.length; i++) {
+    if (which.test(cookieNames[i])) return ctx.cookieJar[cookieNames[i]];
+  }
+  return null;
+};
+
 exports.injectCookies = injectCookies;
 
 exports.get = function(cfg, path, context, getArgs, cb) {
@@ -113,13 +122,13 @@ exports.post = function(cfg, path, context, postArgs, cb) {
       return;
     }
     var headers = {
-      'Content-Type': 'application/x-www-form-urlencoded'
+      'Content-Type': 'application/json'
     };
     injectCookies(context, headers);
 
     if (typeof postArgs === 'object') {
       postArgs['csrf'] = csrf;
-      body = querystring.stringify(postArgs);
+      body = JSON.stringify(postArgs);
       headers['Content-Length'] = body.length;
     }
 
diff --git a/package.json b/package.json
index 99689b57be155e8d11f1206d1cb757f06e029b38..465efdb4ea432f051411094241366ed717d0bc9c 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
         "connect": "1.7.2",
         "convict": "0.0.6",
         "cjson": "0.0.6",
-        "client-sessions": "0.0.3",
+        "client-sessions": "0.0.5",
         "connect-cachify": "0.0.9",
         "connect-cookie-session": "0.0.2",
         "connect-logger-statsd": "0.0.1",
diff --git a/resources/static/communication_iframe/start.js b/resources/static/communication_iframe/start.js
index 386ca965ebb036038498164264af023583456e91..ec70a5b08b97e023640a6b26a45f3233645976be 100644
--- a/resources/static/communication_iframe/start.js
+++ b/resources/static/communication_iframe/start.js
@@ -6,7 +6,8 @@
 (function() {
   var bid = BrowserID,
       network = bid.Network,
-      user = bid.User;
+      user = bid.User,
+      storage = bid.Storage;
 
   network.init();
 
@@ -25,28 +26,71 @@
     }
   }
 
-  chan.bind("getPersistentAssertion", function(trans, params) {
-    setRemoteOrigin(trans.origin);
+  var loggedInUser = undefined;
+
+  // the controlling page may "pause" the iframe when someone else (the dialog)
+  // is supposed to emit events
+  var pause = false;
 
-    trans.delayReturn(true);
+  function checkAndEmit(oncomplete) {
+    if (pause) return;
 
-    user.getPersistentSigninAssertion(function(rv) {
-      trans.complete(rv);
-    }, function() {
-      trans.error();
+    // this will re-certify the user if neccesary
+    user.getSilentAssertion(loggedInUser, function(email, assertion) {
+      if (email) {
+        // only send login events when the assertion is defined - when
+        // the 'loggedInUser' is already logged in, it's false - that is
+        // when the site already has the user logged in and does not want
+        // the resources or cost required to generate an assertion
+        if (assertion) chan.notify({ method: 'login', params: assertion });
+        loggedInUser = email;
+      } else if (loggedInUser !== null) {
+        // only send logout events when loggedInUser is not null, which is an
+        // indicator that the site thinks the user is logged out
+        chan.notify({ method: 'logout' });
+        loggedInUser = null;
+      }
+      oncomplete && oncomplete();
+    }, function(err) {
+      chan.notify({ method: 'logout' });
+      loggedInUser = null;
+      oncomplete && oncomplete();
     });
+  }
+
+  function watchState() {
+    storage.watchLoggedIn(remoteOrigin, checkAndEmit);
+  }
+
+  // one of two events will cause us to begin checking to
+  // see if an event shall be emitted - either an explicit
+  // loggedInUser event or page load.
+  chan.bind("loggedInUser", function(trans, email) {
+    loggedInUser = email;
   });
 
-  chan.bind("logout", function(trans, params) {
+  chan.bind("loaded", function(trans, params) {
     setRemoteOrigin(trans.origin);
+    checkAndEmit(watchState);
+    trans.complete();
+  });
 
-    trans.delayReturn(true);
+  chan.bind("logout", function(trans, params) {
+    if (loggedInUser != null) {
+      storage.setLoggedIn(remoteOrigin, false);
+      chan.notify({ method: 'logout' });
+    }
+  });
 
-    user.clearPersistentSignin(function(rv) {
-      trans.complete(rv);
-    }, function() {
-      trans.error();
-    });
+  chan.bind("dialog_running", function(trans, params) {
+    pause = true;
   });
-}());
 
+  chan.bind("dialog_complete", function(trans, params) {
+    pause = false;
+    // the dialog running can change authentication status,
+    // lets manually purge our network cache
+    network.clearContext();
+    checkAndEmit();
+  });
+}());
diff --git a/resources/static/css/common.css b/resources/static/css/common.css
index c330b9a99a28cd3401a67365026c4ee7d65a6922..b034067e79737569e34fa651addccfc4726a6a90 100644
--- a/resources/static/css/common.css
+++ b/resources/static/css/common.css
@@ -168,6 +168,21 @@ button,
     white-space: nowrap;
 }
 
+button.negative {
+    border: 1px solid #E70227;
+    color: #fff;
+    text-shadow: -1px -1px 0 #E70227;
+
+    -webkit-box-shadow: 0 0 0 1px #FF1560 inset;
+       -moz-box-shadow: 0 0 0 1px #FF1560 inset;
+         -o-box-shadow: 0 0 0 1px #FF1560 inset;
+            box-shadow: 0 0 0 1px #FF1560 inset;
+
+    background-color: #E70227;
+    background-image: -moz-linear-gradient(center top , #FF1560 0pt, #E70227 100%);
+    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #FF1560), color-stop(100%, #E70227));
+}
+
 button:hover,
 button:focus,
 .button:hover,
@@ -178,6 +193,17 @@ button:focus,
 
 }
 
+button.negative:hover,
+button.negative:focus,
+.button.negative:hover,
+.button.negative:focus{
+    background-color: #FF1560;
+    background-image: -moz-linear-gradient(center top , #FF1560 70%, #E70227 100%);
+    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(70%, #FF1560), color-stop(100%, #E70227));
+
+}
+
+
 button:active,
 .button:active {
     background-color: #006EC6;
diff --git a/resources/static/css/style.css b/resources/static/css/style.css
index e2c74432efbaf327f993f5b19397d1f99fa7a50a..23781ec37d78e6dbb92f6b337e3e8432b4f6fd98 100644
--- a/resources/static/css/style.css
+++ b/resources/static/css/style.css
@@ -404,6 +404,13 @@ div.steps {
   margin-right: 0;
 }
 
+#logout_everywhere .completion_text {
+    float: right;
+    display: none;
+    color: #090;
+}
+
+
 button.delete {
   background-color: #EA7676;
   border: 1px solid #B13D3D;
@@ -655,20 +662,6 @@ h1 {
   height: 28px;
 }
 
-#signUpForm .remember {
-  display: inline-block;
-  line-height: 28px;
-}
-
-#signUpForm .remember .checkAlign {
-  float: left;
-}
-
-#signUpForm .remember label {
-  margin-left: 5px;
-  float: left;
-}
-
 #signUpForm .error {
   margin-top: 20px;
   color: red;
diff --git a/resources/static/dialog/controllers/actions.js b/resources/static/dialog/controllers/actions.js
index 320877daf875844026c6909919e5660447cc13c7..315b787cf5b8dc12d0b45261ed2b353ab339bc57 100644
--- a/resources/static/dialog/controllers/actions.js
+++ b/resources/static/dialog/controllers/actions.js
@@ -11,7 +11,7 @@ BrowserID.Modules.Actions = (function() {
       serviceManager = bid.module,
       user = bid.User,
       errors = bid.Errors,
-      wait = bid.Wait,
+      dialogHelpers = bid.Helpers.Dialog,
       runningService,
       onsuccess,
       onerror;
@@ -113,12 +113,15 @@ BrowserID.Modules.Actions = (function() {
       }, self.getErrorDialog(errors.getAssertion));
     },
 
-    doAssertionGenerated: function(assertion) {
+    doAssertionGenerated: function(info) {
       // Clear onerror before the call to onsuccess - the code to onsuccess
       // calls window.close, which would trigger the onerror callback if we
       // tried this afterwards.
-      onerror = null;
-      if(onsuccess) onsuccess(assertion);
+      this.hideWait();
+      dialogHelpers.animateClose(function() {
+        onerror = null;
+        if(onsuccess) onsuccess(info);
+      });
     },
 
     doNotMe: function() {
@@ -151,6 +154,10 @@ BrowserID.Modules.Actions = (function() {
       startService("primary_user_provisioned", info);
     },
 
+    doIsThisYourComputer: function(info) {
+      startService("is_this_your_computer", info);
+    },
+
     doEmailChosen: function(info) {
       startService("email_chosen", info);
     }
diff --git a/resources/static/dialog/controllers/check_registration.js b/resources/static/dialog/controllers/check_registration.js
index 4cf14f76f9b573111f7b15cf6a3f594583461f75..cc7fdf53ed7c68f1e67779c855f95d21c5bc0588 100644
--- a/resources/static/dialog/controllers/check_registration.js
+++ b/resources/static/dialog/controllers/check_registration.js
@@ -32,6 +32,7 @@ BrowserID.Modules.CheckRegistration = (function() {
       var self=this;
       user[self.verifier](self.email, function(status) {
         if (status === "complete") {
+          // TODO - move the syncEmails somewhere else, perhaps into user.js
           user.syncEmails(function() {
             self.close(self.verificationMessage);
             oncomplete && oncomplete();
diff --git a/resources/static/dialog/controllers/email_chosen.js b/resources/static/dialog/controllers/email_chosen.js
index 763b14572cb2fd4eaedde4af53f757155be68c37..8ce4747938702165bd391cb447fbc4176fa9e3fa 100644
--- a/resources/static/dialog/controllers/email_chosen.js
+++ b/resources/static/dialog/controllers/email_chosen.js
@@ -8,7 +8,9 @@ BrowserID.Modules.EmailChosen = (function() {
 
   var bid = BrowserID,
       dialogHelpers = bid.Helpers.Dialog,
-      sc;
+      sc,
+      user = bid.User,
+      storage = bid.Storage;
 
   var EmailChosen = bid.Modules.PageModule.extend({
     start: function(options) {
@@ -20,7 +22,8 @@ BrowserID.Modules.EmailChosen = (function() {
       }
 
       dialogHelpers.getAssertion.call(self, email, options.ready);
-
+      // TODO, this is not needed here, it is done in the state machine.
+      storage.setLoggedIn(user.getOrigin(), options.email);
       sc.start.call(self, options);
     }
   });
diff --git a/resources/static/dialog/controllers/is_this_your_computer.js b/resources/static/dialog/controllers/is_this_your_computer.js
new file mode 100644
index 0000000000000000000000000000000000000000..1c2bc2c9eb3599f62239f7130a074f89f640d672
--- /dev/null
+++ b/resources/static/dialog/controllers/is_this_your_computer.js
@@ -0,0 +1,53 @@
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
+/*global BrowserID:true, PageController: true */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+BrowserID.Modules.IsThisYourComputer = (function() {
+  "use strict";
+
+  var bid = BrowserID,
+      user = bid.User,
+      network = bid.Network,
+      storage = bid.Storage,
+      errors = bid.Errors,
+      email;
+
+  var Module = bid.Modules.PageModule.extend({
+    start: function(options) {
+      options = options || {};
+      email = options.email;
+
+      var self = this;
+
+      self.renderWait("is_this_your_computer", options);
+
+      // TODO - Make the selectors use ids instead of classes.
+      self.click("button.this_is_my_computer", self.yes);
+      self.click("button.this_is_not_my_computer", self.no);
+
+      Module.sc.start.call(self, options);
+    },
+
+    yes: function() {
+      // TODO - Move this to user.js where it could be used by other clients in
+      // other areas.
+      storage.usersComputer.setConfirmed(network.userid());
+      this.confirmed(true);
+    },
+
+    no: function() {
+      storage.usersComputer.setDenied(network.userid());
+      this.confirmed(false);
+    },
+
+    confirmed: function(status) {
+      this.publish("user_computer_status_set", { users_computer: status });
+    }
+
+  });
+
+
+  return Module;
+
+}());
diff --git a/resources/static/dialog/controllers/pick_email.js b/resources/static/dialog/controllers/pick_email.js
index 87a311462475ff78ceae1e4d588dc969a60d5825..3a352f19d60e757caf362bacb00539a9b96568a5 100644
--- a/resources/static/dialog/controllers/pick_email.js
+++ b/resources/static/dialog/controllers/pick_email.js
@@ -51,10 +51,6 @@ BrowserID.Modules.PickEmail = (function() {
       var origin = user.getOrigin();
       storage.site.set(origin, "email", email);
 
-      if (self.allowPersistent) {
-        storage.site.set(origin, "remember", $("#remember").is(":checked"));
-      }
-
       self.close("email_chosen", { email: email });
     }
   }
@@ -90,14 +86,11 @@ BrowserID.Modules.PickEmail = (function() {
 
       options = options || {};
 
-      self.allowPersistent = options.allow_persistent;
       dom.addClass("body", "pickemail");
 
       self.renderDialog("pick_email", {
         identities: getSortedIdentities(),
         siteemail: storage.site.get(origin, "email"),
-        allow_persistent: options.allow_persistent || false,
-        remember: storage.site.get(origin, "remember") || false,
         privacy_url: options.privacyURL,
         tos_url: options.tosURL
       });
diff --git a/resources/static/dialog/controllers/provision_primary_user.js b/resources/static/dialog/controllers/provision_primary_user.js
index 700e113806ce6baa8846a70cc3e3d71ff94c5ad9..367195e4a0191f0054288f801d04534d4362a014 100644
--- a/resources/static/dialog/controllers/provision_primary_user.js
+++ b/resources/static/dialog/controllers/provision_primary_user.js
@@ -6,8 +6,7 @@
 BrowserID.Modules.ProvisionPrimaryUser = (function() {
   "use strict";
 
-  var ANIMATION_TIME = 250,
-      bid = BrowserID,
+  var bid = BrowserID,
       user = bid.User,
       errors = bid.Errors;
 
diff --git a/resources/static/dialog/css/m.css b/resources/static/dialog/css/m.css
index 08b24fe3105acd15ac6dc8915ee5e52af9e22fd4..f3e6dc32525b26460c9cf42e990e374fc336b81d 100644
--- a/resources/static/dialog/css/m.css
+++ b/resources/static/dialog/css/m.css
@@ -139,10 +139,8 @@
     margin-bottom: 20px;
   }
 
-  label[for=remember] {
-    display: block;
-    font-size: 15px;
-    margin-bottom: 25px;
+  .form_section {
+    margin-top: 20px;
   }
 
   #content, .form_section, .inputs, .vertical {
diff --git a/resources/static/dialog/css/popup.css b/resources/static/dialog/css/popup.css
index d3765789e63f85d99d05c94960e50bbb8b3a7c33..c82f4b8246e05d488f068f2a1cb54027467d821c 100644
--- a/resources/static/dialog/css/popup.css
+++ b/resources/static/dialog/css/popup.css
@@ -372,11 +372,6 @@ footer {
     display: none;
 }
 
-label[for=remember] {
-  display: inline-block;
-  margin-bottom: 10px;
-}
-
 a.emphasize {
   background-color: #F0EFED;
   color: #4E4E4E;
@@ -407,3 +402,24 @@ a.emphasize {
 #checkemail {
     text-align: center;
 }
+
+#your_computer_content {
+  width: 90%;
+  margin: auto;
+  text-align: left;
+}
+
+#your_computer_content p button {
+  float: left;
+  margin: 0 1em 0 0;
+  vertical-align: middle;
+  font-size: 1em;
+  width: 4em;
+}
+
+#your_computer_content p {
+  padding-bottom: 1em;
+  line-height: 1.3em;
+  margin-top: 2em;
+  margin-bottom: 2em;
+}
\ No newline at end of file
diff --git a/resources/static/dialog/resources/helpers.js b/resources/static/dialog/resources/helpers.js
index 50db742a114d042af9e667e4ecd9329e7fe1beb0..12029173c581474065a9f0cf4a895003673930cf 100644
--- a/resources/static/dialog/resources/helpers.js
+++ b/resources/static/dialog/resources/helpers.js
@@ -39,13 +39,11 @@
     user.getAssertion(email, user.getOrigin(), function(assert) {
       assert = assert || null;
       wait.hide();
-      animateClose(function() {
-        self.close("assertion_generated", {
-          assertion: assert
-        });
-
-        complete(callback, assert);
+      self.close("assertion_generated", {
+        assertion: assert
       });
+
+      complete(callback, assert);
     }, self.getErrorDialog(errors.getAssertion, complete));
   }
 
@@ -129,7 +127,8 @@
     createUser: createUser,
     addEmail: addEmail,
     resetPassword: resetPassword,
-    cancelEvent: helpers.cancelEvent
+    cancelEvent: helpers.cancelEvent,
+    animateClose: animateClose
   });
 
 }());
diff --git a/resources/static/dialog/resources/internal_api.js b/resources/static/dialog/resources/internal_api.js
index 70d81219a29c5a626d17fa7f18bbfde5dea62678..6285f8cb788357cf7ac11f80b46b76d82f309840 100644
--- a/resources/static/dialog/resources/internal_api.js
+++ b/resources/static/dialog/resources/internal_api.js
@@ -27,7 +27,7 @@
     }
 
     user.checkAuthentication(function onComplete(authenticated) {
-      if(authenticated) {
+      if (authenticated) {
         storage.site.set(origin, "remember", true);
       }
 
diff --git a/resources/static/dialog/resources/state.js b/resources/static/dialog/resources/state.js
index a955170a02bc17f24994a7dc9b72602efaecdc95..6dfa32485ce929e54ebfe992a0efdc95b4b0af00 100644
--- a/resources/static/dialog/resources/state.js
+++ b/resources/static/dialog/resources/state.js
@@ -6,9 +6,9 @@
 BrowserID.State = (function() {
   var bid = BrowserID,
       storage = bid.Storage,
+      network = bid.Network,
       mediator = bid.Mediator,
       helpers = bid.Helpers,
-      publish = mediator.publish.bind(mediator),
       user = bid.User,
       moduleManager = bid.module,
       complete = bid.Helpers.complete,
@@ -20,8 +20,9 @@ BrowserID.State = (function() {
 
   function startStateMachine() {
     var self = this,
-        subscribe = self.subscribe.bind(self),
-        startState = function(save, msg, options) {
+        handleState = self.subscribe.bind(self),
+        redirectToState = mediator.publish.bind(mediator),
+        startAction = function(save, msg, options) {
           if(typeof save !== "boolean") {
             options = msg;
             msg = save;
@@ -33,96 +34,97 @@ BrowserID.State = (function() {
         },
         cancelState = self.popState.bind(self);
 
-    subscribe("start", function(msg, info) {
+    handleState("start", function(msg, info) {
       info = info || {};
 
       self.hostname = info.hostname;
-      self.allowPersistent = !!info.allowPersistent;
       self.privacyURL = info.privacyURL;
       self.tosURL = info.tosURL;
       requiredEmail = info.requiredEmail;
 
       if ((typeof(requiredEmail) !== "undefined") && (!bid.verifyEmail(requiredEmail))) {
         // Invalid format
-        startState("doError", "invalid_required_email", {email: requiredEmail});
+        startAction("doError", "invalid_required_email", {email: requiredEmail});
       }
       else if(info.email && info.type === "primary") {
         primaryVerificationInfo = info;
-        publish("primary_user", info);
+        redirectToState("primary_user", info);
       }
       else {
-        startState("doCheckAuth");
+        startAction("doCheckAuth");
       }
     });
 
-    subscribe("cancel", function() {
-      startState("doCancel");
+    handleState("cancel", function() {
+      startAction("doCancel");
     });
 
-    subscribe("window_unload", function() {
+    handleState("window_unload", function() {
       if (!self.success) {
-        bid.Storage.setStagedOnBehalfOf("");
-        startState("doCancel");
+        storage.setStagedOnBehalfOf("");
+        startAction("doCancel");
       }
     });
 
-    subscribe("authentication_checked", function(msg, info) {
+    handleState("authentication_checked", function(msg, info) {
       var authenticated = info.authenticated;
 
       if (requiredEmail) {
-        startState("doAuthenticateWithRequiredEmail", {
+        self.email = requiredEmail;
+        startAction("doAuthenticateWithRequiredEmail", {
           email: requiredEmail,
           privacyURL: self.privacyURL,
           tosURL: self.tosURL
         });
       }
       else if (authenticated) {
-        publish("pick_email");
+        redirectToState("pick_email");
       } else {
-        publish("authenticate");
+        redirectToState("authenticate");
       }
     });
 
-    subscribe("authenticate", function(msg, info) {
+    handleState("authenticate", function(msg, info) {
       info = info || {};
       info.privacyURL = self.privacyURL;
       info.tosURL = self.tosURL;
-      startState("doAuthenticate", info);
+      startAction("doAuthenticate", info);
     });
 
-    subscribe("user_staged", function(msg, info) {
+    handleState("user_staged", function(msg, info) {
       self.stagedEmail = info.email;
       info.required = !!requiredEmail;
-      startState("doConfirmUser", info);
+      startAction("doConfirmUser", info);
     });
 
-    subscribe("user_confirmed", function() {
-      startState("doEmailConfirmed", { email: self.stagedEmail} );
+    handleState("user_confirmed", function() {
+      self.email = self.stagedEmail;
+      startAction("doEmailConfirmed", { email: self.stagedEmail} );
     });
 
-    subscribe("primary_user", function(msg, info) {
+    handleState("primary_user", function(msg, info) {
       addPrimaryUser = !!info.add;
       email = info.email;
 
       var idInfo = storage.getEmail(email);
       if(idInfo && idInfo.cert) {
-        publish("primary_user_ready", info);
+        redirectToState("primary_user_ready", info);
       }
       else {
         // We don't want to put the provisioning step on the stack, instead when
         // a user cancels this step, they should go back to the step before the
         // provisioning.
-        startState(false, "doProvisionPrimaryUser", info);
+        startAction(false, "doProvisionPrimaryUser", info);
       }
     });
 
-    subscribe("primary_user_provisioned", function(msg, info) {
+    handleState("primary_user_provisioned", function(msg, info) {
       info = info || {};
       info.add = !!addPrimaryUser;
-      startState("doPrimaryUserProvisioned", info);
+      startAction("doPrimaryUserProvisioned", info);
     });
 
-    subscribe("primary_user_unauthenticated", function(msg, info) {
+    handleState("primary_user_unauthenticated", function(msg, info) {
       info = helpers.extend(info || {}, {
         add: !!addPrimaryUser,
         email: email,
@@ -134,49 +136,50 @@ BrowserID.State = (function() {
       if(primaryVerificationInfo) {
         primaryVerificationInfo = null;
         if(requiredEmail) {
-          startState("doCannotVerifyRequiredPrimary", info);
+          startAction("doCannotVerifyRequiredPrimary", info);
         }
         else if(info.add) {
           // Add the pick_email in case the user cancels the add_email screen.
           // The user needs something to go "back" to.
-          publish("pick_email");
-          publish("add_email", info);
+          redirectToState("pick_email");
+          redirectToState("add_email", info);
         }
         else {
-          publish("authenticate", info);
+          redirectToState("authenticate", info);
         }
       }
       else {
-        startState("doVerifyPrimaryUser", info);
+        startAction("doVerifyPrimaryUser", info);
       }
     });
 
-    subscribe("primary_user_authenticating", function(msg, info) {
+    handleState("primary_user_authenticating", function(msg, info) {
       // Keep the dialog from automatically closing when the user browses to
       // the IdP for verification.
       moduleManager.stopAll();
       self.success = true;
     });
 
-    subscribe("primary_user_ready", function(msg, info) {
-      startState("doEmailChosen", info);
+    handleState("primary_user_ready", function(msg, info) {
+      redirectToState("email_chosen", info);
     });
 
-    subscribe("pick_email", function() {
-      startState("doPickEmail", {
+    handleState("pick_email", function() {
+      startAction("doPickEmail", {
         origin: self.hostname,
-        allow_persistent: self.allowPersistent,
         privacyURL: self.privacyURL,
         tosURL: self.tosURL
       });
     });
 
-    subscribe("email_chosen", function(msg, info) {
+    handleState("email_chosen", function(msg, info) {
       info = info || {};
 
       var email = info.email,
           idInfo = storage.getEmail(email);
 
+      self.email = email;
+
       function oncomplete() {
         complete(info.complete);
       }
@@ -184,23 +187,22 @@ BrowserID.State = (function() {
       if(idInfo) {
         if(idInfo.type === "primary") {
           if(idInfo.cert) {
-            startState("doEmailChosen", info);
+            startAction("doEmailChosen", info);
           }
           else {
             // If the email is a primary, and their cert is not available,
             // throw the user down the primary flow.
             // Doing so will catch cases where the primary certificate is expired
-            // and the user must re-verify with their IdP.  This flow will
-            // generate its own assertion when ready.
-            publish("primary_user", info);
+            // and the user must re-verify with their IdP.
+            redirectToState("primary_user", info);
           }
         }
         else {
           user.checkAuthentication(function(authentication) {
             if(authentication === "assertion") {
-              // user not authenticated, kick them over to the required email
-              // screen.
-              startState("doAuthenticateWithRequiredEmail", {
+              // user must authenticate with their password, kick them over to
+              // the required email screen to enter the password.
+              startAction("doAuthenticateWithRequiredEmail", {
                 email: email,
                 secondary_auth: true,
                 privacyURL: self.privacyURL,
@@ -208,7 +210,7 @@ BrowserID.State = (function() {
               });
             }
             else {
-              startState("doEmailChosen", info);
+              startAction("doEmailChosen", info);
             }
             oncomplete();
           }, oncomplete);
@@ -219,59 +221,79 @@ BrowserID.State = (function() {
       }
     });
 
-    subscribe("notme", function() {
-      startState("doNotMe");
+    handleState("notme", function() {
+      startAction("doNotMe");
     });
 
-    subscribe("logged_out", function() {
-      publish("authenticate");
+    handleState("logged_out", function() {
+      redirectToState("authenticate");
     });
 
-    subscribe("authenticated", function(msg, info) {
-      publish("email_chosen", info);
+    handleState("authenticated", function(msg, info) {
+      redirectToState("email_chosen", info);
     });
 
-    subscribe("forgot_password", function(msg, info) {
+    handleState("forgot_password", function(msg, info) {
       // forgot password initiates the forgotten password flow.
-      startState(false, "doForgotPassword", info);
+      startAction(false, "doForgotPassword", info);
     });
 
-    subscribe("reset_password", function(msg, info) {
+    handleState("reset_password", function(msg, info) {
       // reset password says the password has been reset, now waiting for
       // confirmation.
-      startState(false, "doResetPassword", info);
+      startAction(false, "doResetPassword", info);
     });
 
-    subscribe("assertion_generated", function(msg, info) {
+    handleState("assertion_generated", function(msg, info) {
       self.success = true;
       if (info.assertion !== null) {
-        startState("doAssertionGenerated", info.assertion);
+        if (storage.usersComputer.shouldAsk(network.userid())) {
+          // We have to confirm the user's status
+          self.assertion_info = info;
+          redirectToState("is_this_your_computer", info);
+        }
+        else {
+          storage.setLoggedIn(user.getOrigin(), self.email);
+          startAction("doAssertionGenerated", { assertion: info.assertion, email: self.email });
+        }
       }
       else {
-        publish("pick_email");
+        redirectToState("pick_email");
       }
     });
 
-    subscribe("add_email", function(msg, info) {
+    handleState("is_this_your_computer", function(msg, info) {
+      startAction("doIsThisYourComputer", info);
+    });
+
+    handleState("user_computer_status_set", function(msg, info) {
+      // User's status has been confirmed, redirect them back to the
+      // assertion_generated state with the stored assertion_info
+      var assertion_info = self.assertion_info;
+      self.assertion_info = null;
+      redirectToState("assertion_generated", assertion_info);
+    });
+
+    handleState("add_email", function(msg, info) {
       info = helpers.extend(info || {}, {
         privacyURL: self.privacyURL,
         tosURL: self.tosURL
       });
 
-      startState("doAddEmail", info);
+      startAction("doAddEmail", info);
     });
 
-    subscribe("email_staged", function(msg, info) {
+    handleState("email_staged", function(msg, info) {
       self.stagedEmail = info.email;
       info.required = !!requiredEmail;
-      startState("doConfirmEmail", info);
+      startAction("doConfirmEmail", info);
     });
 
-    subscribe("email_confirmed", function() {
-      startState("doEmailConfirmed", { email: self.stagedEmail} );
+    handleState("email_confirmed", function() {
+      startAction("doEmailConfirmed", { email: self.stagedEmail} );
     });
 
-    subscribe("cancel_state", function(msg, info) {
+    handleState("cancel_state", function(msg, info) {
       cancelState(info);
     });
 
diff --git a/resources/static/dialog/start.js b/resources/static/dialog/start.js
index 8768ea7acd0e2a85d013d2175bb3d189a11ac534..970034d1afe9fea098c09969146267db1c8ac527 100644
--- a/resources/static/dialog/start.js
+++ b/resources/static/dialog/start.js
@@ -24,6 +24,7 @@
       moduleManager.register("authenticate", modules.Authenticate);
       moduleManager.register("check_registration", modules.CheckRegistration);
       moduleManager.register("forgot_password", modules.ForgotPassword);
+      moduleManager.register("is_this_your_computer", modules.IsThisYourComputer);
       moduleManager.register("pick_email", modules.PickEmail);
       moduleManager.register("required_email", modules.RequiredEmail);
       moduleManager.register("verify_primary_user", modules.VerifyPrimaryUser);
@@ -33,7 +34,6 @@
       moduleManager.register("xhr_delay", modules.XHRDelay);
       moduleManager.register("xhr_disable_form", modules.XHRDisableForm);
 
-
       moduleManager.start("xhr_delay");
       moduleManager.start("xhr_disable_form");
       moduleManager.start("dialog");
diff --git a/resources/static/dialog/views/is_this_your_computer.ejs b/resources/static/dialog/views/is_this_your_computer.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..2ab5bd1789788cf4abb72f2860898db25bd4982c
--- /dev/null
+++ b/resources/static/dialog/views/is_this_your_computer.ejs
@@ -0,0 +1,20 @@
+<% /* This Source Code Form is subject to the terms of the Mozilla Public
+      License, v. 2.0. If a copy of the MPL was not distributed with this
+      file, You can obtain one at http://mozilla.org/MPL/2.0/. */ %>
+
+  <div id="your_computer_content">
+    <h2><%= gettext('If you don\'t mind me asking, is this your computer?') %></h2>
+
+    <p>
+      <button class="this_is_my_computer" tabindex="3"><%= gettext('yes') %></button>
+      <%= gettext('If so, we\'ll keep you logged in for a couple weeks.') %>
+    </p>
+
+    <p>
+      <button class="this_is_not_my_computer negative" tabindex="3"><%= gettext('no') %></button>
+      <%= gettext('If you\'re at a public computer such as a library or internet cafe, we\'ll ask you ' +
+                  'for your password again in an hour.') %>
+    </p>
+  </div>
+
+
diff --git a/resources/static/dialog/views/pick_email.ejs b/resources/static/dialog/views/pick_email.ejs
index be2fdd3fae2d01aadfb975af222eeedfaa79154d..a9dbdae0af95ae247065e29a8b318bfa83185357 100644
--- a/resources/static/dialog/views/pick_email.ejs
+++ b/resources/static/dialog/views/pick_email.ejs
@@ -23,28 +23,18 @@
 
 
       <div class="submit add cf">
-      <% if (allow_persistent) { %>
-          <label for="remember" class="selectable">
-            <input type="checkbox" id="remember" name="remember" <% if (remember) { %> checked="checked" <% } %> />
-            <%= gettext('Always sign in using this email') %>
-          </label>
-      <% } %>
-
-      <% if (privacy_url && tos_url) { %>
-        <p>
-<%= format(
-          gettext('By clicking %s, you confirm that you accept this site\'s <a %s>Terms of Use</a> and <a %s>Privacy Policy</a>.'),
-                   [ gettext('sign in'),
-                     format(' href="%s" target="_new"', [tos_url]),
-                     format(' href="%s" target="_new"', [privacy_url])
-                   ]) %>
-        </p>
-        <p>
-      <% } %>
-          <button id="signInButton"><%= gettext('sign in') %></button>
       <% if (privacy_url && tos_url) { %>
+        <p class="tospp">
+          <%= format(
+            gettext('By clicking %s, you confirm that you accept this site\'s <a %s>Terms of Use</a> and <a %s>Privacy Policy</a>.'),
+                    [ gettext('sign in'),
+                      format(' href="%s" target="_new"', [tos_url]),
+                      format(' href="%s" target="_new"', [privacy_url])
+                    ]) %>
         </p>
       <% } %>
+
+        <button id="signInButton"><%= gettext('sign in') %></button>
+        <br style="clear: both" />
       </div>
   </div>
-
diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js
index 89fef47ee479e6352bff774465fe3fb99b370beb..01a67fe39fd6e13c5c74aa6ea2997deb292bd5ab 100644
--- a/resources/static/include_js/include.js
+++ b/resources/static/include_js/include.js
@@ -918,26 +918,11 @@
     };
   }());
 
-
-  // this is for calls that are non-interactive
-  function _open_hidden_iframe(doc) {
-    var iframe = doc.createElement("iframe");
-    iframe.style.display = "none";
-    doc.body.appendChild(iframe);
-    iframe.src = ipServer + "/communication_iframe";
-    return iframe;
-  }
-
-  /**
-   * The meat and potatoes of the verified email protocol
-   */
-
-
   if (!navigator.id) {
     navigator.id = {};
   }
 
-  if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) {
+  if (!navigator.id.request || navigator.id._shimmed) {
     var ipServer = "https://browserid.org";
     var userAgent = navigator.userAgent;
     // We must check for both XUL and Java versions of Fennec.  Both have
@@ -951,105 +936,223 @@
 
     var w;
 
-    navigator.id.get = function(callback, options) {
-      if (typeof callback !== 'function') {
-        throw "navigator.id.get() requires a callback argument";
+    // table of registered event listeners
+    var listeners = {
+      login: [ ],
+      logout: [ ],
+      loginCanceled: [ ]
+    };
+
+    var compatMode = undefined;
+    function checkCompat(requiredMode) {
+      if (requiredMode === true) {
+        try { console.log("this site uses deprecated APIs (see documentation for navigator.id.request())"); } catch(e) { }
       }
 
-      if (options && options.silent) {
-        _noninteractiveCall('getPersistentAssertion', { }, function(rv) {
-          callback(rv);
-        }, function(e, msg) {
-          callback(null);
-        });
-      } else {
-        // focus an existing window
-        if (w) {
-          try {
-            w.focus();
+      if (compatMode === undefined) compatMode = requiredMode;
+      else if (compatMode != requiredMode) {
+        throw "you cannot combine browserid event APIs with navigator.id.getVerifiedEmail() or navigator.id.get()" +
+              "this site should instead use navigator.id.request() and the browserid event API";
+      }
+    }
+
+    function emitEvent(type, params) {
+      if (listeners[type]) {
+        var evt = document.createEvent('Event');
+        evt.initEvent(type, true, true);
+        // XXX: we should probably implement .stopImmediatePropagation()
+        if (params) {
+          for (var k in params) {
+            if (params.hasOwnProperty(k)) {
+              evt[k] = params[k];
+            }
           }
-          catch(e) {
-            /* IE7 blows up here, do nothing */
+        }
+        for (var i = 0; i < listeners[type].length; i++) {
+          try {
+            listeners[type][i](evt);
+          } catch(e) {
+            // XXX: what shall we do when an exception is raised by an event handler?
           }
-          return;
         }
+      }
+    }
 
-        if (!BrowserSupport.isSupported()) {
-          var reason = BrowserSupport.getNoSupportReason(),
-              url = "unsupported_dialog";
-
-          if(reason === "LOCALSTORAGE_DISABLED") {
-            url = "cookies_disabled";
+    var commChan;
+
+    // this is for calls that are non-interactive
+    function _open_hidden_iframe() {
+      if (!commChan) {
+        var doc = window.document;
+        var iframe = doc.createElement("iframe");
+        iframe.style.display = "none";
+        doc.body.appendChild(iframe);
+        iframe.src = ipServer + "/communication_iframe";
+        commChan = Channel.build({
+          window: iframe.contentWindow,
+          origin: ipServer,
+          scope: "mozid_ni",
+          onReady: function() {
+            // once the channel is set up, we'll fire a loaded message.  this is the
+            // cutoff point where we'll say if 'setLoggedInUser' was not called before
+            // this point, then it wont be called (XXX: optimize and improve me)
+            commChan.call({ method: 'loaded', success: function(){}, error: function() {} });
           }
+        });
 
-          w = window.open(
-            ipServer + "/" + url,
-            null,
-            windowOpenOpts);
-          return;
-        }
+        commChan.bind('logout', function(trans, params) {
+          emitEvent('logout');
+        });
 
-        w = WinChan.open({
-          url: ipServer + '/sign_in',
-          relay_url: ipServer + '/relay',
-          window_features: windowOpenOpts,
-          params: {
-            method: "get",
-            params: options
-          }
-        }, function(err, r) {
-          // clear the window handle
-          w = undefined;
-          // ignore err!
-          callback(err ? null : (r ? r : null));
+        commChan.bind('login', function(trans, params) {
+          emitEvent('login', { assertion: params });
         });
       }
+    }
+
+    function internalAddEventListener(type, listener) {
+      // add event to listeners table if it's not there already
+      if (!listeners[type]) throw "unsupported event type: '" + type + "'";
+
+      // is the function already registered?
+      for (var i = 0; i < listeners[type].length; i++) {
+        if (listeners[type][i] === listener) return;
+      }
+      listeners[type].push(listener);
+    }
+
+    navigator.id.addEventListener = function(type, listener) {
+      checkCompat(false);
+
+      // allocate iframe if it is not allocated
+      _open_hidden_iframe();
+      internalAddEventListener(type,listener);
+    };
+
+    function internalRemoveEventListener(type, listener ) {
+      // remove event from listeners table
+      var i;
+      for (i = 0; i < listeners[type].length; i++) {
+        if (listeners[type][i] === listener) break;
+      }
+      if (i < listeners[type][i].length) {
+        listeners[type].splice(i, 1);
+      }
+    }
+
+    navigator.id.removeEventListener = function(type, listener/*, useCapture */) {
+      checkCompat(false);
+      internalRemoveEventListener(type, listener);
     };
 
-    navigator.id.getVerifiedEmail = function (callback, options) {
-      if (options) {
-        throw "getVerifiedEmail doesn't accept options.  use navigator.id.get() instead.";
+    navigator.id.logout = function() {
+      checkCompat(false);
+
+      // allocate iframe if it is not allocated
+      _open_hidden_iframe();
+
+      // send logout message
+      commChan.notify({ method: 'logout' });
+    };
+
+    navigator.id.setLoggedInUser = function(email) {
+      checkCompat(false);
+
+      // 1. allocate iframe if it is not allocated
+      _open_hidden_iframe();
+
+      // 2. send a "loggedInUser" message to iframe
+      commChan.notify({ method: 'loggedInUser', params: email });
+    };
+
+    // backwards compatibility function
+    navigator.id.get = function(callback, options) {
+      checkCompat(true);
+
+      if (options && options.silent) {
+        if (callback) setTimeout(function() { callback(null); }, 0);
+      } else {
+        function handleEvent(e) {
+          internalRemoveEventListener('login', handleEvent);
+          callback((e && e.assertion) ? e.assertion : null);
+        }
+        internalAddEventListener('login', handleEvent);
+        internalRequest(options);
       }
+    };
+
+    // backwards compatibility function
+    navigator.id.getVerifiedEmail = function(callback) {
+      checkCompat(true);
       navigator.id.get(callback);
     };
 
-    navigator.id.logout = function(callback) {
-      _noninteractiveCall('logout', { }, function(rv) {
-        callback(rv);
-      }, function() {
-        callback(null);
-      });
+    navigator.id.request = function(options) {
+      checkCompat(false);
+      return internalRequest(options);
     };
 
-    var _noninteractiveCall = function(method, args, onsuccess, onerror) {
-      var doc = window.document;
-      var ni_iframe = _open_hidden_iframe(doc);
+    function internalRequest(options) {
+      // focus an existing window
+      if (w) {
+        try {
+          w.focus();
+        }
+        catch(e) {
+          /* IE7 blows up here, do nothing */
+        }
+        return;
+      }
+
+      if (!BrowserSupport.isSupported()) {
+        var reason = BrowserSupport.getNoSupportReason(),
+        url = "unsupported_dialog";
 
-      var chan = Channel.build({window: ni_iframe.contentWindow, origin: ipServer, scope: "mozid_ni"});
+        if(reason === "LOCALSTORAGE_DISABLED") {
+          url = "cookies_disabled";
+        }
 
-      function cleanup() {
-        chan.destroy();
-        chan = undefined;
-        doc.body.removeChild(ni_iframe);
+        w = window.open(
+          ipServer + "/" + url,
+          null,
+          windowOpenOpts);
+        return;
       }
 
-      chan.call({
-        method: method,
-        params: args,
-        success: function(rv) {
-          if (onsuccess) {
-            onsuccess(rv);
+      // notify the iframe that the dialog is running so we
+      // don't do duplicative work
+      if (commChan) commChan.notify({ method: 'dialog_running' });
+
+      w = WinChan.open({
+        url: ipServer + '/sign_in',
+        relay_url: ipServer + '/relay',
+        window_features: windowOpenOpts,
+        params: {
+          method: "get",
+          params: options
+        }
+      }, function(err, r) {
+        // unpause the iframe to detect future changes in login state
+        if (commChan) {
+          // update the loggedInUser in the case that an assertion was generated, as
+          // this will prevent the comm iframe from thinking that state has changed
+          // and generating a new assertion.  IF, however, this request is not a success,
+          // then we do not change the loggedInUser - and we will let the comm frame determine
+          // if generating a logout event is the right thing to do
+          if (!err && r && r.email) {
+            commChan.notify({ method: 'loggedInUser', params: r.email });
           }
-          cleanup();
-        },
-        error: function(code, msg) {
-          if (onerror) onerror(code, msg);
-          cleanup();
+          commChan.notify({ method: 'dialog_complete' });
         }
+
+        // clear the window handle
+        w = undefined;
+        if (!err && r && r.assertion) emitEvent('login', { assertion: r.assertion });
+        else emitEvent('loginCanceled');
       });
     };
 
-    navigator.id._getVerifiedEmailIsShimmed = true;
+    navigator.id._shimmed = true;
   }
 }());
 
diff --git a/resources/static/pages/manage_account.js b/resources/static/pages/manage_account.js
index 9bb92ea310d2570617ea49a45a78dcb5aa8d947d..59bc9ccc3154516c4956f224e02b4e13ecff9938 100644
--- a/resources/static/pages/manage_account.js
+++ b/resources/static/pages/manage_account.js
@@ -96,6 +96,11 @@ BrowserID.manageAccount = (function() {
     }
   }
 
+  function logoutEverywhere(oncomplete) {
+    storage.logoutEverywhere();
+    setTimeout(oncomplete, 0);
+  }
+
   function startEdit(event) {
     // XXX add some helpers in the dom library to find section.
     event.preventDefault();
@@ -194,6 +199,13 @@ BrowserID.manageAccount = (function() {
 
     dom.bindEvent("button.edit", "click", startEdit);
     dom.bindEvent("button.done", "click", cancelEdit);
+    dom.bindEvent("button.logout_everywhere", "click", function() {
+      logoutEverywhere(function() {
+        $("button.logout_everywhere").fadeOut(700, function() {
+          $("#logout_everywhere .completion_text").show();
+        });
+      });
+    });
     dom.bindEvent("#edit_password_form", "submit", cancelEvent(changePassword));
 
     user.checkAuthentication(function(auth_level) {
diff --git a/resources/static/shared/browserid.js b/resources/static/shared/browserid.js
index a129416825e0e7dce113a463ab03d41ea04d8e00..34481fb5c7a0a74fff17cce319daf5134d49d546 100644
--- a/resources/static/shared/browserid.js
+++ b/resources/static/shared/browserid.js
@@ -7,5 +7,9 @@
 
   window.BrowserID = window.BrowserID || {};
 
-
+  // Define some constants.
+  _.extend(window.BrowserID, {
+    // always use 1024 DSA keys - see issue #1293
+    KEY_LENGTH: 128
+  });
 }());
diff --git a/resources/static/shared/network.js b/resources/static/shared/network.js
index a91dc475e0885c4b463f635d858bfb7f5f32e6c5..7e8834285df71ed5a38f36308dc85ce882f3dd5a 100644
--- a/resources/static/shared/network.js
+++ b/resources/static/shared/network.js
@@ -13,11 +13,25 @@ BrowserID.Network = (function() {
       domain_key_creation_time,
       auth_status,
       code_version,
+      userid,
       time_until_delay,
       mediator = bid.Mediator,
       xhr = bid.XHR,
       post = xhr.post,
-      get = xhr.get;
+      get = xhr.get,
+      storage = bid.Storage;
+
+  function setUserID(uid) {
+    userid = uid;
+
+    // TODO - Get this out of here and put it into user!
+
+    // when session context returns with an authenticated user, update localstorage
+    // to indicate we've seen this user on this device
+    if (userid) {
+      storage.usersComputer.setSeen(userid);
+    }
+  }
 
   function onContextChange(msg, result) {
     context = result;
@@ -28,6 +42,7 @@ BrowserID.Network = (function() {
     domain_key_creation_time = result.domain_key_creation_time;
     auth_status = result.auth_level;
     code_version = result.code_version;
+    setUserID(result.userid);
 
     // seed the PRNG
     // FIXME: properly abstract this out, probably by exposing a jwcrypto
@@ -54,6 +69,11 @@ BrowserID.Network = (function() {
 
       if (typeof authenticated !== 'boolean') throw status;
 
+      // now update the userid which is set once the user is authenticated.
+      // this is used to key off client side state, like whether this user has
+      // confirmed ownership of this device
+      setUserID(status.userid);
+
       // at this point we know the authentication status of the
       // session, let's set it to perhaps save a network request
       // (to fetch session context).
@@ -90,7 +110,8 @@ BrowserID.Network = (function() {
         url: "/wsapi/authenticate_user",
         data: {
           email: email,
-          pass: password
+          pass: password,
+          ephemeral: !storage.usersComputer.confirmed(email)
         },
         success: handleAuthenticationResponse.curry("password", onComplete, onFailure),
         error: onFailure
@@ -111,7 +132,8 @@ BrowserID.Network = (function() {
         url: "/wsapi/auth_with_assertion",
         data: {
           email: email,
-          assertion: assertion
+          assertion: assertion,
+          ephemeral: !storage.usersComputer.confirmed(email)
         },
         success: handleAuthenticationResponse.curry("assertion", onComplete, onFailure),
         error: onFailure
@@ -135,6 +157,14 @@ BrowserID.Network = (function() {
       }, onFailure);
     },
 
+    /**
+     * clear local cache, including authentication status and
+     * other session data.
+     *
+     * @method clearContext
+     */
+    clearContext: clearContext,
+
     /**
      * Log the authenticated user out
      * @method logout
@@ -151,6 +181,7 @@ BrowserID.Network = (function() {
           // FIXME: we should return a confirmation that the
           // user was successfully logged out.
           auth_status = false;
+          setUserID(undefined);
           complete(onComplete);
         },
         error: function(info, xhr, textStatus) {
@@ -487,7 +518,8 @@ BrowserID.Network = (function() {
         url: "/wsapi/cert_key",
         data: {
           email: email,
-          pubkey: pubkey.serialize()
+          pubkey: pubkey.serialize(),
+          ephemeral: !storage.usersComputer.confirmed(email)
         },
         success: onComplete,
         error: onFailure
@@ -501,11 +533,30 @@ BrowserID.Network = (function() {
     listEmails: function(onComplete, onFailure) {
       get({
         url: "/wsapi/list_emails",
-        success: onComplete,
+        success: function(emails) {
+          // TODO - Put this into user.js or storage.js when emails are synced/saved to
+          // storage.
+          // update our local storage map of email addresses to user ids
+          if (userid) {
+            storage.updateEmailToUserIDMapping(userid, _.keys(emails));
+          }
+
+          onComplete && onComplete(emails);
+        },
         error: onFailure
       });
     },
 
+    /**
+     * Return the user's userid, which will an integer if the user
+     * is authenticated, undefined otherwise.
+     *
+     * @method userid
+     */
+    userid: function() {
+      return userid;
+    },
+
     /**
      * Get the current time on the server in the form of a
      * date object.
diff --git a/resources/static/shared/provisioning.js b/resources/static/shared/provisioning.js
index 5c90de51a6143241c73c611a8a5a16d5cd252886..4eba6b1561caf66f590a4b528f72de6335344439 100644
--- a/resources/static/shared/provisioning.js
+++ b/resources/static/shared/provisioning.js
@@ -28,7 +28,7 @@ BrowserID.Provisioning = (function() {
 
     if (!failureCB) throw "missing required failure callback";
 
-    if (!args || !args.email || !args.url) {
+    if (!args || !args.email || !args.url || !args.hasOwnProperty('ephemeral')) {
       return fail('internal', 'missing required arguments');
     }
 
@@ -61,21 +61,15 @@ BrowserID.Provisioning = (function() {
     chan.bind('beginProvisioning', function(trans, s) {
       return {
         email: args.email,
-        // XXX: certificate duration should vary depending on a variety of factors:
-        //   * user is on a device that is not her own
-        //   * user is in an environment that can't handle the crypto
-        cert_duration_s: (6 * 60 * 60)
+        // XXX: {non,}ephemeral auth duration should be stored somewhere central and
+        // should be common between primary and secondary cert provisioning.  Because
+        // the latter occurs on the server, it should probably be sent session_context.
+        cert_duration_s: ((args.ephemeral === false) ? (6 * 60 * 60) : (60 * 60))
       };
     });
 
     chan.bind('genKeyPair', function(trans, s) {
-      // this will take a little bit
-      // FIXME: refactor so code that makes this decision is shared.
-      var keysize = 256;
-      var ie_version = BrowserID.BrowserSupport.getInternetExplorerVersion();
-      if (ie_version > -1 && ie_version < 9)
-        keysize = 128;
-      keypair = jwk.KeyPair.generate("DS", keysize);
+      keypair = jwk.KeyPair.generate("DS", BrowserID.KEY_LENGTH);
       return keypair.publicKey.toSimpleObject();
     });
 
diff --git a/resources/static/shared/storage.js b/resources/static/shared/storage.js
index 40288033af84e75d874a8fc4f677bd31b1649d8a..f395fcb1856575d3a4e923ee4b8471b855476923 100644
--- a/resources/static/shared/storage.js
+++ b/resources/static/shared/storage.js
@@ -5,7 +5,8 @@
 BrowserID.Storage = (function() {
 
   var jwk,
-      storage = localStorage;
+      storage = localStorage,
+      ONE_DAY_IN_MS = (1000 * 60 * 60 * 24);
 
   function prepareDeps() {
     if (!jwk) {
@@ -159,7 +160,6 @@ BrowserID.Storage = (function() {
     }
   }
 
-
   function managePageGet(key) {
     var allInfo = JSON.parse(storage.managePage || "{}");
     return allInfo[key];
@@ -176,6 +176,190 @@ BrowserID.Storage = (function() {
     delete allInfo[key];
     storage.managePage = JSON.stringify(allInfo);
   }
+
+  function setLoggedIn(origin, email) {
+    var allInfo = JSON.parse(storage.loggedIn || "{}");
+    if (email) allInfo[origin] = email;
+    else delete allInfo[origin];
+    storage.loggedIn = JSON.stringify(allInfo);
+  }
+
+  function getLoggedIn(origin) {
+    var allInfo = JSON.parse(storage.loggedIn || "{}");
+    return allInfo[origin];
+  }
+
+  function watchLoggedIn(origin, callback) {
+    var lastState = getLoggedIn(origin);
+
+    function checkState() {
+      var currentState = getLoggedIn(origin);
+      if (lastState !== currentState) {
+        callback();
+        lastState = currentState;
+      };
+    }
+
+    // IE8 does not have addEventListener, nor does it support storage events.
+    if (window.addEventListener) window.addEventListener('storage', checkState, false);
+    else window.setInterval(checkState, 2000);
+  }
+  function logoutEverywhere() {
+    storage.loggedIn = "{}";
+  }
+
+  function mapEmailToUserID(emailOrUserID) {
+    if (typeof(emailOrUserID) === 'number') return emailOrUserID;
+    var allInfo = JSON.parse(storage.emailToUserID || "{}");
+    return allInfo[emailOrUserID];
+  }
+
+  // tools to manage knowledge of whether this is the user's computer,
+  // which helps us set appropriate authentication duration.
+  function validState(state) {
+    return (state === 'seen' || state === 'confirmed' || state === 'denied');
+  }
+
+  function setConfirmationState(userid, state) {
+    userid = mapEmailToUserID(userid);
+
+    if (typeof userid !== 'number') throw 'bad userid ' + userid;
+
+    if (!validState(state)) throw "invalid state";
+
+    var allInfo;
+    var currentState;
+    var lastUpdated = 0;
+
+    try {
+      allInfo = JSON.parse(storage.usersComputer);
+      if (typeof allInfo !== 'object') throw 'bogus';
+
+      var userInfo = allInfo[userid];
+      if (userInfo) {
+        currentState = userInfo.state;
+        lastUpdated = Date.parse(userInfo.updated);
+
+        if (!validState(currentState)) throw "corrupt/outdated";
+        if (NaN === lastUpdated) throw "corrupt/outdated";
+      }
+    } catch(e) {
+      currentState = undefined;
+      lastUpdated = 0;
+      allInfo = {};
+    }
+
+    // ...now determine if we should update the state...
+
+    // first if the user said this wasn't their computer over 24 hours ago,
+    // forget that setting (we will revisit this)
+    if (currentState === 'denied' &&
+        ((new Date()).getTime() - lastUpdated) > ONE_DAY_IN_MS) {
+      currentState = undefined;
+      lastUpdated = 0;
+    }
+
+    // if the user has a non-null state and this is another user sighting
+    // (seen), then forget it
+    if (state === 'seen' && currentState) return;
+
+    // good to go!  let's make the update
+    allInfo[userid] = {state: state, updated: new Date().toString()};
+    storage.usersComputer = JSON.stringify(allInfo);
+  }
+
+  function userConfirmedOnComputer(userid) {
+    try {
+      userid = mapEmailToUserID(userid);
+      var allInfo = JSON.parse(storage.usersComputer || "{}");
+      return allInfo[userid].state === 'confirmed';
+    } catch(e) {
+      return false;
+    }
+  }
+
+  function shouldAskUserAboutHerComputer(userid) {
+    // we should ask the user if this is their computer if they were
+    // first seen over a minute ago, if they haven't denied ownership
+    // of this computer in the last 24 hours, and they haven't confirmed
+    // ownership of this computer
+    try {
+      userid = mapEmailToUserID(userid);
+      var allInfo = JSON.parse(storage.usersComputer);
+      var userInfo = allInfo[userid];
+      if(userInfo) {
+        var s = userInfo.state;
+        var timeago = new Date() - Date.parse(userInfo.updated);
+
+        // The ask state is an artificial state that should never be seen in
+        // the wild.  It is used in testing.
+        if (s === 'ask') return true;
+        if (s === 'confirmed') return false;
+        if (s === 'denied' && timeago > ONE_DAY_IN_MS) return true;
+        if (s === 'seen' && timeago > (60 * 1000)) return true;
+      }
+    } catch (e) {
+      return true;
+    }
+
+    return false;
+  }
+
+  function setUserSeenOnComputer(userid) {
+    setConfirmationState(userid, 'seen');
+  }
+
+  function setUserConfirmedOnComputer(userid) {
+    setConfirmationState(userid, 'confirmed');
+  }
+
+  function setNotMyComputer(userid) {
+    setConfirmationState(userid, 'denied');
+  }
+
+  function setUserMustConfirmComputer(userid) {
+      try {
+        userid = mapEmailToUserID(userid);
+        var allInfo = JSON.parse(storage.usersComputer);
+        if (typeof allInfo !== 'object') throw 'bogus';
+
+        var userInfo = allInfo[userid] || {};
+        userInfo.state = 'ask';
+        storage.usersComputer = JSON.stringify(allInfo);
+      } catch(e) {}
+  }
+
+  function clearUsersComputerOwnershipStatus(userid) {
+    try {
+      allInfo = JSON.parse(storage.usersComputer);
+      if (typeof allInfo !== 'object') throw 'bogus';
+
+      var userInfo = allInfo[userid];
+      if (userInfo) {
+        allInfo[userid] = null;
+        delete allInfo[userid];
+        storage.usersComputer = JSON.stringify(allInfo);
+      }
+    } catch (e) {}
+  }
+
+  // update our local storage based mapping of email addresses to userids,
+  // this map helps us determine whether a specific email address belongs
+  // to a user who has already confirmed their ownership of a computer.
+  function updateEmailToUserIDMapping(userid, emails) {
+    var allInfo;
+    try {
+      allInfo = JSON.parse(storage.emailToUserID);
+      if (typeof allInfo != 'object' || allInfo === null) throw "bogus";
+    } catch(e) {
+      allInfo = {};
+    }
+    _.each(emails, function(email) {
+      allInfo[email] = userid;
+    });
+    storage.emailToUserID = JSON.stringify(allInfo);
+  }
+
   return {
     /**
      * Add an email address and optional key pair.
@@ -242,6 +426,80 @@ BrowserID.Storage = (function() {
       remove: managePageRemove
     },
 
+    usersComputer: {
+      /**
+       * Query whether the user has confirmed that this is their computer
+       * @param {integer} userid - the user's numeric id, returned from session_context when authed.
+       * @method usersComputer.confirmed */
+      confirmed: userConfirmedOnComputer,
+      /**
+       * Save the fact that a user confirmed that this is their computer
+       * @param {integer} userid - the user's numeric id, returned from session_context when authed.
+       * @method usersComputer.setConfirmed */
+      setConfirmed: setUserConfirmedOnComputer,
+      /**
+       * Save the fact that a user denied that this is their computer
+       * @param {integer} userid - the user's numeric id, returned from session_context when authed.
+       * @method usersComputer.setDenied */
+      setDenied: setNotMyComputer,
+      /**
+       * Should we ask the user if this is their computer, based on the last
+       * time they used browserid and the last time they answered a question
+       * about this device
+       * @param {integer} userid - the user's numeric id, returned
+       *   from session_context when authed.
+       * @method usersComputer.seen */
+      shouldAsk: shouldAskUserAboutHerComputer,
+      /**
+       * Save the fact that a user has been seen on this computer before, but do not overwrite
+       *  existing state
+       * @param {integer} userid - the user's numeric id, returned from session_context when authed.
+       * @method usersComputer.setSeen */
+      setSeen: setUserSeenOnComputer,
+      /**
+       * Clear the status for the user
+       * @param {integer} userid - the user's numeric id, returned from session_context when authed.
+       * @method usersComputer.clear */
+      clear: clearUsersComputerOwnershipStatus,
+      /**
+       * Force the user to be asked their status
+       * @param {integer} userid - the user's numeric id, returned from session_context when authed.
+       * @method usersComputer.forceAsk */
+      forceAsk: setUserMustConfirmComputer
+    },
+
+    /** add email addresses to the email addy to userid mapping used when we're trying to determine
+     * if a user has used this computer before and what their auth duration should be
+     * @param {number} userid - the userid of the user
+     * @param {array} emails - a list of email addresses belonging to the user
+     * @returns zilch
+     */
+    updateEmailToUserIDMapping: updateEmailToUserIDMapping,
+
+    /** set logged in state for a site
+     * @param {string} origin - the site to set logged in state for
+     * @param {string} email - the email that the user is logged in with or falsey if login state should be cleared
+     */
+    setLoggedIn: setLoggedIn,
+
+    /** check if the user is logged into a site
+     * @param {string} origin - the site to set check the logged in state of
+     * @returns the email with which the user is logged in
+     */
+    getLoggedIn: getLoggedIn,
+
+    /** watch for changes in the logged in state of a page
+     * @param {string} origin - the site to watch the status of
+     * @param {function} callback - a callback to invoke when state changes
+     */
+    watchLoggedIn: watchLoggedIn,
+
+    /** clear all logged in preferences
+     * @param {string} origin - the site to watch the status of
+     * @param {function} callback - a callback to invoke when state changes
+     */
+    logoutEverywhere: logoutEverywhere,
+
     /**
      * Clear all stored data - email addresses, key pairs, temporary key pairs,
      * site/email associations.
diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js
index d4a23aec70aacea814a0e07caa9c6b341bc52c08..e1fe4502ff97e6eba2c196df3d16b4be2094989f 100644
--- a/resources/static/shared/user.js
+++ b/resources/static/shared/user.js
@@ -33,9 +33,9 @@ BrowserID.User = (function() {
         // if it was issued *before* the domain key was last updated or
         // if the certificate expires in less that 5 minutes from now.
         function isExpired(cert) {
-          // if it expires in less than 5 minutes, it's too old to use.
+          // if it expires in less than 2 minutes, it's too old to use.
           var diff = cert.expires.valueOf() - serverTime.valueOf();
-          if (diff < (60 * 5 * 1000)) {
+          if (diff < (60 * 2 * 1000)) {
             return true;
           }
 
@@ -436,7 +436,11 @@ BrowserID.User = (function() {
       }
 
       provisioning(
-        { email: email, url: info.prov },
+        {
+          email: email,
+          url: info.prov,
+          ephemeral: !storage.usersComputer.confirmed(email)
+        },
         function(keypair, cert) {
           var userInfo = _.extend({
             keypair: keypair,
@@ -458,7 +462,6 @@ BrowserID.User = (function() {
           }
         }
       );
-
     },
 
     /**
@@ -626,6 +629,10 @@ BrowserID.User = (function() {
      * @param {function} [onFailure] - called on error.
      */
     logoutUser: function(onComplete, onFailure) {
+      // logout of all websites
+      storage.logoutEverywhere();
+
+      // log out of browserid
       network.logout(function() {
         setAuthenticationStatus(false);
         if (onComplete) {
@@ -905,12 +912,7 @@ BrowserID.User = (function() {
      */
     syncEmailKeypair: function(email, onComplete, onFailure) {
       prepareDeps();
-      // FIXME: parameterize!
-      var keysize = 256;
-      var ie_version = BrowserID.BrowserSupport.getInternetExplorerVersion();
-      if (ie_version > -1 && ie_version < 9)
-        keysize = 128;
-      var keypair = jwk.KeyPair.generate("DS", keysize);
+      var keypair = jwk.KeyPair.generate("DS", bid.KEY_LENGTH);
       setTimeout(function() {
         certifyEmailKeypair(email, keypair, onComplete, onFailure);
       }, 0);
@@ -1046,27 +1048,38 @@ BrowserID.User = (function() {
     },
 
     /**
-     * Get an assertion for the current domain, as long as the user has
-     * selected that they want the email/site remembered
+     * Get an assertion for the current domain if the user is signed into it
      * @method getPersistentSigninAssertion
      * @param {function} onComplete - called on completion.  Called with an
      * assertion if successful, null otw.
      * @param {function} onFailure - called on XHR failure.
      */
-    getPersistentSigninAssertion: function(onComplete, onFailure) {
+    getSilentAssertion: function(siteSpecifiedEmail, onComplete, onFailure) {
+      // XXX: why do we need to check authentication status here explicitly.
+      //      why can't we fail later?  the problem with doing this is that
+      //      knowing correct present authentication status requires that we
+      //      talk to the server, because you can be logged in or logged out
+      //      in many different contexts (dialog, manage page, cookies expire).
+      //      so if we rely on localstorage only and check authentication status
+      //      only when we know a network request will be required, we very well
+      //      might have fewer race conditions and do fewer network requests.
       User.checkAuthentication(function(authenticated) {
         if (authenticated) {
-          var remembered = storage.site.get(origin, "remember");
-          var email = storage.site.get(origin, "email");
-          if (remembered && email) {
-            User.getAssertion(email, origin, onComplete, onFailure);
-          }
-          else if (onComplete) {
-            onComplete(null);
+          var loggedInEmail = storage.getLoggedIn(origin);
+          if (loggedInEmail !== siteSpecifiedEmail) {
+            if (loggedInEmail) {
+              User.getAssertion(loggedInEmail, origin, function(assertion) {
+                onComplete(assertion ? loggedInEmail : null, assertion);
+              }, onFailure);
+            } else {
+              onComplete(null, null);
+            }
+          } else {
+            onComplete(loggedInEmail, null);
           }
         }
         else if (onComplete) {
-          onComplete(null);
+          onComplete(null, null);
         }
       }, onFailure);
     },
@@ -1078,15 +1091,14 @@ BrowserID.User = (function() {
      * a boolean, true if successful, false otw.
      * @param {function} onFailure - called on XHR failure.
      */
-    clearPersistentSignin: function(onComplete, onFailure) {
+    logout: function(onComplete, onFailure) {
       User.checkAuthentication(function(authenticated) {
         if (authenticated) {
-          storage.site.set(origin, "remember", false);
-          if (onComplete) {
-            onComplete(true);
-          }
-        } else if (onComplete) {
-          onComplete(false);
+          storage.setLoggedIn(origin, false);
+        }
+
+        if (onComplete) {
+          onComplete(!!authenticated);
         }
       }, onFailure);
     },
@@ -1111,8 +1123,6 @@ BrowserID.User = (function() {
 
       onComplete(hasSecondary);
     }
-
-
   };
 
   User.setOrigin(document.location.host);
diff --git a/resources/static/shared/xhr.js b/resources/static/shared/xhr.js
index c174b189652dd6e002507d9158e74bd168d15791..5c22abb882086c7cee5a3a3b728c6f19ca616c1b 100644
--- a/resources/static/shared/xhr.js
+++ b/resources/static/shared/xhr.js
@@ -136,7 +136,9 @@ BrowserID.XHR = (function() {
 
       var req = _.extend(options, {
         type: "POST",
-        data: data,
+        data: JSON.stringify(data),
+        contentType: 'application/json',
+        processData: false,
         defer_success: true
       });
       request(req);
diff --git a/resources/static/test/cases/controllers/is_this_your_computer.js b/resources/static/test/cases/controllers/is_this_your_computer.js
new file mode 100644
index 0000000000000000000000000000000000000000..8e6702c9bb7e4bb29eeadb1dc3119434460a054c
--- /dev/null
+++ b/resources/static/test/cases/controllers/is_this_your_computer.js
@@ -0,0 +1,50 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+(function() {
+  "use strict";
+
+  var controller,
+      el = $("body"),
+      bid = BrowserID,
+      user = bid.User,
+      xhr = bid.Mocks.xhr,
+      modules = bid.Modules,
+      testHelpers = bid.TestHelpers,
+      register = testHelpers.register;
+
+
+  module("controllers/is_this_your_computer", {
+    setup: function() {
+      testHelpers.setup();
+    },
+
+    teardown: function() {
+      if (controller) {
+        try {
+          controller.destroy();
+          controller = null;
+        } catch(e) {
+          // could already be destroyed from the close
+        }
+      }
+      testHelpers.teardown();
+    }
+  });
+
+  function createController(options) {
+    controller = modules.IsThisYourComputer.create();
+    controller.start(options || {});
+  }
+
+  test("yes - sets ownership flag to true for the user", function() {
+    console.log("add a test");
+  });
+
+  test("no - set the ownership flag to false for the user", function() {
+    console.log("add a test");
+  });
+}());
+
diff --git a/resources/static/test/cases/controllers/pick_email.js b/resources/static/test/cases/controllers/pick_email.js
index 0e6f1b0c1a2de7b2fe365852383be493163b5fbe..9d3678f8c2c53b28dc7fc9c2d4050513038082ef 100644
--- a/resources/static/test/cases/controllers/pick_email.js
+++ b/resources/static/test/cases/controllers/pick_email.js
@@ -32,11 +32,9 @@
   });
 
 
-  function createController(allowPersistent) {
+  function createController() {
     controller = bid.Modules.PickEmail.create();
-    controller.start({
-      allow_persistent: allowPersistent || false
-    });
+    controller.start({});
   }
 
   test("multiple emails - print emails in alphabetical order", function() {
@@ -78,73 +76,26 @@
     equal(label.hasClass("preselected"), false, "the label has no class");
   });
 
-  function testRemember(allowPersistent, remember) {
+  asyncTest("signIn - saves picked email to storage", function() {
     storage.addEmail("testuser@testuser.com", {});
     storage.addEmail("testuser2@testuser.com", {});
-    storage.site.set(testOrigin, "remember", remember);
-
-    createController(allowPersistent);
-
-    // remember can only be checked if allowPersistent is allowed
-    var rememberChecked = allowPersistent ? remember : false;
-
-    equal($("#remember").is(":checked"), rememberChecked, "remember should " + (rememberChecked ? "" : " not " ) + " be checked");
-  }
-
-  test("pickemail controller with allow_persistent and remember set to false", function() {
-    testRemember(false, false);
-  });
-
-  test("pickemail controller with allow_persistent set to false and remember set to true", function() {
-    testRemember(false, true);
-  });
-
-  test("pickemail controller with allow_persistent and remember set to true", function() {
-    testRemember(true, true);
-  });
 
-
-  asyncTest("signIn saves email, remember status to storage when allow_persistent set to true", function() {
-    storage.addEmail("testuser@testuser.com", {});
-    storage.addEmail("testuser2@testuser.com", {});
-
-    createController(true);
+    createController();
 
     $("input[type=radio]").eq(0).trigger("click");
-    $("#remember").attr("checked", true);
 
     var assertion;
 
     register("email_chosen", function(msg, info) {
       equal(storage.site.get(testOrigin, "email"), "testuser2@testuser.com", "email saved correctly");
-      equal(storage.site.get(testOrigin, "remember"), true, "remember saved correctly");
       ok(info.email, "email_chosen message triggered with email");
       start();
     });
     controller.signIn();
   });
 
-  asyncTest("signIn saves email, but not remember status when allow_persistent set to false", function() {
-    storage.addEmail("testuser@testuser.com", {});
-    storage.addEmail("testuser2@testuser.com", {});
-    storage.site.set(testOrigin, "remember", false);
-
-    createController(false);
-
-    $("input[type=radio]").eq(0).trigger("click");
-    $("#remember").attr("checked", true);
-
-    register("email_chosen", function(msg, info) {
-      equal(storage.site.get(testOrigin, "email"), "testuser2@testuser.com", "email saved correctly");
-      equal(storage.site.get(testOrigin, "remember"), false, "remember saved correctly");
-
-      start();
-    });
-    controller.signIn();
-  });
-
   asyncTest("addEmail triggers an 'add_email' message", function() {
-    createController(false);
+    createController();
 
     register("add_email", function(msg, info) {
       ok(true, "add_email triggered");
@@ -157,7 +108,7 @@
     storage.addEmail("testuser2@testuser.com", {});
     storage.addEmail("testuser@testuser.com", {});
 
-    createController(false);
+    createController();
 
     equal($("#email_1").is(":checked"), false, "radio button is not selected before click.");
 
@@ -174,7 +125,7 @@
     storage.addEmail("testuser+test0@testuser.com", {});
     storage.addEmail("testuser+test1@testuser.com", {});
 
-    createController(false);
+    createController();
 
     equal($("#email_1").is(":checked"), false, "radio button is not selected before click.");
 
@@ -187,30 +138,5 @@
     equal($("#email_0").is(":checked"), true, "radio button is correctly selected");
   });
 
-  test("click on the 'Always sign in...' label and checkbox - correct toggling", function() {
-    createController(true);
-
-    var label = $("label[for=remember]"),
-        checkbox = $("#remember").removeAttr("checked");
-
-    equal(checkbox.is(":checked"), false, "checkbox is not yet checked");
-
-    // toggle checkbox to on clicking on label
-    label.trigger("click");
-    equal(checkbox.is(":checked"), true, "checkbox is correctly checked");
-
-    // toggle checkbox to off clicking on label
-    label.trigger("click");
-    equal(checkbox.is(":checked"), false, "checkbox is correctly unchecked");
-
-    // toggle checkbox to on clicking on checkbox
-    checkbox.trigger("click");
-    equal(checkbox.is(":checked"), true, "checkbox is correctly checked");
-
-    // toggle checkbox to off clicking on checkbox
-    checkbox.trigger("click");
-    equal(checkbox.is(":checked"), false, "checkbox is correctly unchecked");
-  });
-
 }());
 
diff --git a/resources/static/test/cases/include.js b/resources/static/test/cases/include.js
index 5c54c0bcfc9a4ac47cb3acb4b5bf3896827a030e..42aa6c6562488a4dfba94cba7b438641c8d0ba02 100644
--- a/resources/static/test/cases/include.js
+++ b/resources/static/test/cases/include.js
@@ -12,10 +12,18 @@
     equal(typeof navigator.id, "object", "navigator.id namespace is available");
   });
 
-  test("navigator.id.getVerifiedEmail is available", function() {
-    equal(typeof navigator.id.getVerifiedEmail, "function", "navigator.id.getVerifiedEmail is available");
+  test("expected public API functions available", function() {
+    _.each([
+      "get",
+      "request",
+      "setLoggedInUser",
+      "logout",
+      "addEventListener",
+      "removeEventListener"
+    ], function(item, index) {
+      equal(typeof navigator.id[ item ], "function", "navigator.id." + item + " is available");
+    });
   });
 
-
 }());
 
diff --git a/resources/static/test/cases/resources/state.js b/resources/static/test/cases/resources/state.js
index c951c5ec98f1b11a55af5b52b98c58a17522a534..1c32995418eb58c6e2c785a5530d5a8bd791f23b 100644
--- a/resources/static/test/cases/resources/state.js
+++ b/resources/static/test/cases/resources/state.js
@@ -12,9 +12,11 @@
       user = bid.User,
       machine,
       actions,
+      network = bid.Network,
       storage = bid.Storage,
       testHelpers = bid.TestHelpers,
-      xhr = bid.Mocks.xhr;
+      xhr = bid.Mocks.xhr,
+      TEST_EMAIL = "testuser@testuser.com";
 
   var ActionsMock = function() {
     this.called = {};
@@ -38,6 +40,19 @@
     machine.start({controller: actions});
   }
 
+  function setContextInfo(auth_status) {
+    // Make sure there is context info for network.
+    var serverTime = (new Date().getTime()) - 10;
+    mediator.publish("context_info", {
+      server_time: serverTime,
+      domain_key_creation_time: serverTime,
+      code_version: "ABCDEF",
+      auth_status: auth_status || "password",
+      userid: 1,
+      random_seed: "ABCDEFGH"
+    });
+  }
+
   module("resources/state", {
     setup: function() {
       testHelpers.setup();
@@ -51,10 +66,6 @@
   });
 
 
-  test("can create and start the machine", function() {
-    ok(machine, "Machine has been created");
-  });
-
   test("attempt to create a state machine without a controller", function() {
     var error;
     try {
@@ -69,15 +80,15 @@
 
   test("user_staged - call doConfirmUser", function() {
     mediator.publish("user_staged", {
-      email: "testuser@testuser.com"
+      email: TEST_EMAIL
     });
 
-    equal(actions.info.doConfirmUser.email, "testuser@testuser.com", "waiting for email confirmation for testuser@testuser.com");
+    equal(actions.info.doConfirmUser.email, TEST_EMAIL, "waiting for email confirmation for testuser@testuser.com");
   });
 
   test("user_staged with required email - call doConfirmUser with required = true", function() {
-    mediator.publish("start", { requiredEmail: "testuser@testuser.com" });
-    mediator.publish("user_staged", { email: "testuser@testuser.com" });
+    mediator.publish("start", { requiredEmail: TEST_EMAIL });
+    mediator.publish("user_staged", { email: TEST_EMAIL });
 
     equal(actions.info.doConfirmUser.required, true, "doConfirmUser called with required flag");
   });
@@ -89,31 +100,31 @@
   });
 
   test("email_staged - call doConfirmEmail", function() {
-    mediator.publish("email_staged", { email: "testuser@testuser.com" });
+    mediator.publish("email_staged", { email: TEST_EMAIL });
 
     equal(actions.info.doConfirmEmail.required, false, "doConfirmEmail called without required flag");
   });
 
   test("email_staged with required email - call doConfirmEmail with required = true", function() {
-    mediator.publish("start", { requiredEmail: "testuser@testuser.com" });
-    mediator.publish("email_staged", { email: "testuser@testuser.com" });
+    mediator.publish("start", { requiredEmail: TEST_EMAIL });
+    mediator.publish("email_staged", { email: TEST_EMAIL });
 
     equal(actions.info.doConfirmEmail.required, true, "doConfirmEmail called with required flag");
   });
 
   test("primary_user with already provisioned primary user - call doEmailChosen", function() {
-    storage.addEmail("testuser@testuser.com", { type: "primary", cert: "cert" });
-    mediator.publish("primary_user", { email: "testuser@testuser.com" });
+    storage.addEmail(TEST_EMAIL, { type: "primary", cert: "cert" });
+    mediator.publish("primary_user", { email: TEST_EMAIL });
     ok(actions.called.doEmailChosen, "doEmailChosen called");
   });
 
   test("primary_user with unprovisioned primary user - call doProvisionPrimaryUser", function() {
-    mediator.publish("primary_user", { email: "testuser@testuser.com" });
+    mediator.publish("primary_user", { email: TEST_EMAIL });
     ok(actions.called.doProvisionPrimaryUser, "doPrimaryUserProvisioned called");
   });
 
   test("primary_user_provisioned - call doEmailChosen", function() {
-    mediator.publish("primary_user_provisioned", { email: "testuser@testuser.com" });
+    mediator.publish("primary_user_provisioned", { email: TEST_EMAIL });
     ok(actions.called.doPrimaryUserProvisioned, "doPrimaryUserProvisioned called");
   });
 
@@ -124,19 +135,19 @@
   });
 
   test("primary_user_unauthenticated after required email - call doCannotVerifyRequiredPrimary", function() {
-    mediator.publish("start", { requiredEmail: "testuser@testuser.com", type: "primary", add: false, email: "testuser@testuser.com" });
+    mediator.publish("start", { requiredEmail: TEST_EMAIL, type: "primary", add: false, email: TEST_EMAIL });
     mediator.publish("primary_user_unauthenticated");
     ok(actions.called.doCannotVerifyRequiredPrimary, "doCannotVerifyRequiredPrimary called");
   });
 
   test("primary_user_unauthenticated after verification of new user - call doAuthenticate", function() {
-    mediator.publish("start", { email: "testuser@testuser.com", type: "primary", add: false });
+    mediator.publish("start", { email: TEST_EMAIL, type: "primary", add: false });
     mediator.publish("primary_user_unauthenticated");
     ok(actions.called.doAuthenticate, "doAuthenticate called");
   });
 
   test("primary_user_unauthenticated after verification of additional email to current user - call doPickEmail and doAddEmail", function() {
-    mediator.publish("start", { email: "testuser@testuser.com", type: "primary", add: true });
+    mediator.publish("start", { email: TEST_EMAIL, type: "primary", add: true });
     mediator.publish("primary_user_unauthenticated");
     ok(actions.called.doPickEmail, "doPickEmail called");
     ok(actions.called.doAddEmail, "doAddEmail called");
@@ -153,39 +164,46 @@
   });
 
   test("primary_user - call doProvisionPrimaryUser", function() {
-    mediator.publish("primary_user", { email: "testuser@testuser.com", assertion: "assertion" });
+    mediator.publish("primary_user", { email: TEST_EMAIL, assertion: "assertion" });
 
     ok(actions.called.doProvisionPrimaryUser, "doProvisionPrimaryUser called");
   });
 
-  test("primary_user_ready - call doEmailChosen", function() {
-    mediator.publish("primary_user_ready", { email: "testuser@testuser.com", assertion: "assertion" });
+  asyncTest("primary_user_ready - redirect to `email_chosen`", function() {
+    storage.addEmail(TEST_EMAIL, {});
+    mediator.subscribe("email_chosen", function(msg, info) {
+      equal(info.email, TEST_EMAIL, "correct email passed");
+      start();
+    });
 
-    ok(actions.called.doEmailChosen, "doEmailChosen called");
-  });
+    mediator.publish("primary_user_ready", { email: TEST_EMAIL, assertion: "assertion" });
 
-  test("authenticated - call doEmailChosen", function() {
-    storage.addEmail("testuser@testuser.com", {});
-    mediator.publish("authenticated", { email: "testuser@testuser.com" });
+  });
 
-    ok(actions.called.doEmailChosen, "doEmailChosen has been called");
+  asyncTest("authenticated - redirect to `email_chosen`", function() {
+    storage.addEmail(TEST_EMAIL, {});
+    mediator.subscribe("email_chosen", function(msg, data) {
+      equal(data.email, TEST_EMAIL);
+      start();
+    });
+    mediator.publish("authenticated", { email: TEST_EMAIL });
   });
 
   test("forgot_password", function() {
     mediator.publish("forgot_password", {
-      email: "testuser@testuser.com",
+      email: TEST_EMAIL,
       requiredEmail: true
     });
-    equal(actions.info.doForgotPassword.email, "testuser@testuser.com", "correct email passed");
+    equal(actions.info.doForgotPassword.email, TEST_EMAIL, "correct email passed");
     equal(actions.info.doForgotPassword.requiredEmail, true, "correct requiredEmail passed");
   });
 
   test("reset_password - call doResetPassword", function() {
     // XXX how is this different from forgot_password?
     mediator.publish("reset_password", {
-      email: "testuser@testuser.com"
+      email: TEST_EMAIL
     });
-    equal(actions.info.doResetPassword.email, "testuser@testuser.com", "reset password with the correct email");
+    equal(actions.info.doResetPassword.email, TEST_EMAIL, "reset password with the correct email");
   });
 
   test("cancel reset_password flow - go two steps back", function() {
@@ -193,34 +211,50 @@
     // screens back.  Do do this, we are simulating the steps necessary to get
     // to the reset_password flow.
     mediator.publish("authenticate");
-    mediator.publish("forgot_password", undefined, { email: "testuser@testuser.com" });
+    mediator.publish("forgot_password", undefined, { email: TEST_EMAIL });
     mediator.publish("reset_password");
     actions.info.doAuthenticate = {};
     mediator.publish("cancel_state");
-    equal(actions.info.doAuthenticate.email, "testuser@testuser.com", "authenticate called with the correct email");
+    equal(actions.info.doAuthenticate.email, TEST_EMAIL, "authenticate called with the correct email");
   });
 
-  test("assertion_generated with null assertion", function() {
+  asyncTest("assertion_generated with null assertion - redirect to pick_email", function() {
+    mediator.subscribe("pick_email", function() {
+      ok(true, "redirect to pick_email");
+      start();
+    });
     mediator.publish("assertion_generated", {
       assertion: null
     });
-
-    equal(actions.called.doPickEmail, true, "now picking email because of null assertion");
   });
 
-  test("assertion_generated with assertion", function() {
+  asyncTest("assertion_generated with assertion, need to ask user whether it's their computer - redirect to is_this_your_computer", function() {
+    setContextInfo("password");
+    storage.usersComputer.forceAsk(network.userid());
+    mediator.subscribe("is_this_your_computer", function() {
+      ok(true, "redirect to is_this_your_computer");
+      start();
+    });
+
     mediator.publish("assertion_generated", {
       assertion: "assertion"
     });
-
-    equal(actions.info.doAssertionGenerated, "assertion", "assertion generated with good assertion");
   });
 
-  test("add_email - call doAddEmail", function() {
-    mediator.publish("add_email", { email: "testuser@testuser.com" });
+  test("assertion_generated with assertion, do not ask user whether it's their computer - doAssertionGenerated called", function() {
+    setContextInfo("password");
+    // First, set up the context info for the email.
 
-    ok(actions.called.doAddEmail, "user wants to add an email");
-    ok(actions.info.doAddEmail.email, "testuser@testuser.com", "correct email passed");
+    storage.addEmail(TEST_EMAIL, {});
+    mediator.publish("email_chosen", { email: TEST_EMAIL });
+    mediator.publish("assertion_generated", {
+      assertion: "assertion"
+    });
+
+    equal(actions.info.doAssertionGenerated.assertion, "assertion",
+        "doAssertionGenerated called with assertion");
+    equal(actions.info.doAssertionGenerated.email, TEST_EMAIL,
+        "doAssertionGenerated called with email");
   });
 
   test("email_confirmed", function() {
@@ -247,10 +281,10 @@
 
   test("authenticate", function() {
     mediator.publish("authenticate", {
-      email: "testuser@testuser.com"
+      email: TEST_EMAIL
     });
 
-    equal(actions.info.doAuthenticate.email, "testuser@testuser.com", "authenticate with testuser@testuser.com");
+    equal(actions.info.doAuthenticate.email, TEST_EMAIL, "authenticate with testuser@testuser.com");
   });
 
   test("start with no special parameters - go straight to checking auth", function() {
@@ -276,19 +310,19 @@
   });
 
   test("start with valid requiredEmail - go to doCheckAuth", function() {
-    mediator.publish("start", { requiredEmail: "testuser@testuser.com" });
+    mediator.publish("start", { requiredEmail: TEST_EMAIL });
 
     equal(actions.called.doCheckAuth, true, "checking auth on start");
   });
 
   asyncTest("start to complete successful primary email verification - goto 'primary_user'", function() {
     mediator.subscribe("primary_user", function(msg, info) {
-      equal(info.email, "testuser@testuser.com", "correct email given");
+      equal(info.email, TEST_EMAIL, "correct email given");
       equal(info.add, true, "correct add flag");
       start();
     });
 
-    mediator.publish("start", { email: "testuser@testuser.com", type: "primary", add: true });
+    mediator.publish("start", { email: TEST_EMAIL, type: "primary", add: true });
   });
 
   test("cancel", function() {
@@ -299,7 +333,7 @@
 
 
   asyncTest("email_chosen with secondary email, user must authenticate - call doAuthenticateWithRequiredEmail", function() {
-    var email = "testuser@testuser.com";
+    var email = TEST_EMAIL;
     storage.addEmail(email, { type: "secondary" });
 
     xhr.setContextInfo("auth_level", "assertion");
@@ -314,7 +348,7 @@
   });
 
   asyncTest("email_chosen with secondary email, user authenticated to secondary - call doEmailChosen", function() {
-    var email = "testuser@testuser.com";
+    var email = TEST_EMAIL;
     storage.addEmail(email, { type: "secondary" });
     xhr.setContextInfo("auth_level", "password");
 
@@ -334,7 +368,7 @@
     // generate its own assertion when ready.  For efficiency, we could
     // check here whether the cert is ready, but it is early days yet and
     // the format may change.
-    var email = "testuser@testuser.com";
+    var email = TEST_EMAIL;
     storage.addEmail(email, { type: "primary" });
     mediator.publish("email_chosen", { email: email });
 
@@ -342,7 +376,7 @@
   });
 
   test("email_chosen with invalid email - throw exception", function() {
-    var email = "testuser@testuser.com",
+    var email = TEST_EMAIL,
         error;
 
     try {
@@ -355,11 +389,17 @@
   });
 
   test("null assertion generated - preserve original options in doPickEmail", function() {
-    mediator.publish("start", { allowPersistent: true });
+    mediator.publish("start", {
+      hostname: "http://example.com",
+      privacyURL: "http://example.com/priv.html",
+      tosURL: "http://example.com/tos.html"
+    });
     mediator.publish("assertion_generated", { assertion: null });
 
     equal(actions.called.doPickEmail, true, "doPickEmail callled");
-    equal(actions.info.doPickEmail.allow_persistent, true, "allow_persistent preserved");
+    equal(actions.info.doPickEmail.origin, "http://example.com", "hostname preserved");
+    equal(actions.info.doPickEmail.privacyURL, "http://example.com/priv.html", "privacyURL preserved");
+    equal(actions.info.doPickEmail.tosURL, "http://example.com/tos.html", "tosURL preserved");
   });
 
 }());
diff --git a/resources/static/test/cases/shared/user.js b/resources/static/test/cases/shared/user.js
index 3fd25130ee4260378c98effe2d0e7e06eaccf052..976b55de08e10a565d26acedbfcabf42e1a40b1e 100644
--- a/resources/static/test/cases/shared/user.js
+++ b/resources/static/test/cases/shared/user.js
@@ -1015,95 +1015,22 @@ var vep = require("./vep");
     failureCheck(lib.cancelUser);
   });
 
-  asyncTest("getPersistentSigninAssertion with invalid login - expect null assertion", function() {
-    xhr.setContextInfo("auth_level", undefined);
-
-    lib.syncEmailKeypair("testuser@testuser.com", function() {
-      storage.site.set(testOrigin, "remember", false);
-      storage.site.set(testOrigin, "email", "testuser@testuser.com");
-      xhr.useResult("invalid");
-
-      lib.getPersistentSigninAssertion(function onComplete(assertion) {
-        strictEqual(assertion, null, "assertion with invalid login is null");
-        start();
-      }, testHelpers.unexpectedXHRFailure);
-    }, testHelpers.unexpectedXHRFailure);
-  });
-
-  asyncTest("getPersistentSigninAssertion without email set for site - expect null assertion", function() {
-    xhr.setContextInfo("auth_level", "primary");
-    storage.site.set(testOrigin, "remember", true);
-    storage.site.remove(testOrigin, "email");
-
-    lib.getPersistentSigninAssertion(function onComplete(assertion) {
-      strictEqual(assertion, null, "assertion with no email is null");
-      start();
-    }, testHelpers.unexpectedXHRFailure);
-  });
-
-  asyncTest("getPersistentSigninAssertion without remember set for site - expect null assertion", function() {
-    xhr.setContextInfo("auth_level", "primary");
-    lib.syncEmailKeypair("testuser@testuser.com", function() {
-      storage.site.set(testOrigin, "remember", false);
-      storage.site.set(testOrigin, "email", "testuser@testuser.com");
-      // invalidate the email so that we force a fresh key certification with
-      // the server
-      storage.invalidateEmail("testuser@testuser.com");
-
-      lib.getPersistentSigninAssertion(function onComplete(assertion) {
-        strictEqual(assertion, null, "assertion with remember=false is null");
-        start();
-      }, testHelpers.unexpectedXHRFailure);
-    });
-  });
-
-  asyncTest("getPersistentSigninAssertion with valid login, email, and remember set to true - expect assertion", function() {
-    xhr.setContextInfo("auth_level", "primary");
-    lib.syncEmailKeypair("testuser@testuser.com", function() {
-      storage.site.set(testOrigin, "remember", true);
-      storage.site.set(testOrigin, "email", "testuser@testuser.com");
-      // invalidate the email so that we force a fresh key certification with
-      // the server
-      storage.invalidateEmail("testuser@testuser.com");
-
-      lib.getPersistentSigninAssertion(function onComplete(assertion) {
-        ok(assertion, "we have an assertion!");
-        start();
-      }, testHelpers.unexpectedXHRFailure);
-    });
-  });
-
-  asyncTest("getPersistentSigninAssertion with XHR failure", function() {
-    xhr.setContextInfo("auth_level", "primary");
-    lib.syncEmailKeypair("testuser@testuser.com", function() {
-      storage.site.set(testOrigin, "remember", true);
-      storage.site.set(testOrigin, "email", "testuser@testuser.com");
-      // invalidate the email so that we force a fresh key certification with
-      // the server
-      storage.invalidateEmail("testuser@testuser.com");
-
-      failureCheck(lib.getPersistentSigninAssertion);
-    });
-
-
-  });
 
-  asyncTest("clearPersistentSignin with invalid login", function() {
+  asyncTest("logout with invalid login", function() {
     xhr.setContextInfo("auth_level", undefined);
 
-    lib.clearPersistentSignin(function onComplete(success) {
+    lib.logout(function onComplete(success) {
       strictEqual(success, false, "success with invalid login is false");
       start();
     }, testHelpers.unexpectedXHRFailure);
   });
 
-  asyncTest("clearPersistentSignin with valid login with remember set to true", function() {
+  asyncTest("logout with valid login with remember set to true", function() {
     xhr.setContextInfo("auth_level", "primary");
     storage.site.set(testOrigin, "remember", true);
 
-    lib.clearPersistentSignin(function onComplete(success) {
+    lib.logout(function onComplete(success) {
       strictEqual(success, true, "success flag good");
-      strictEqual(storage.site.get(testOrigin, "remember"), false, "remember flag set to false");
       start();
     }, testHelpers.unexpectedXHRFailure);
   });
diff --git a/resources/static/test/mocks/xhr.js b/resources/static/test/mocks/xhr.js
index 6b82fb2362359f5607173d8f1a2679a60c50160c..e33be1b0dfde34ddbf28f6dbdb0a896fb7a20e73 100644
--- a/resources/static/test/mocks/xhr.js
+++ b/resources/static/test/mocks/xhr.js
@@ -34,12 +34,12 @@ BrowserID.Mocks.xhr = (function() {
       "get /wsapi/email_for_token?token=token valid": { email: "testuser@testuser.com" },
       "get /wsapi/email_for_token?token=token needsPassword": { email: "testuser@testuser.com", needs_password: true },
       "get /wsapi/email_for_token?token=token invalid": { success: false },
-      "post /wsapi/authenticate_user valid": { success: true },
+      "post /wsapi/authenticate_user valid": { success: true, userid: 1 },
       "post /wsapi/authenticate_user invalid": { success: false },
       "post /wsapi/authenticate_user incorrectPassword": { success: false },
       "post /wsapi/authenticate_user ajaxError": undefined,
-      "post /wsapi/auth_with_assertion primary": { success: true },
-      "post /wsapi/auth_with_assertion valid": { success: true },
+      "post /wsapi/auth_with_assertion primary": { success: true, userid: 1 },
+      "post /wsapi/auth_with_assertion valid": { success: true, userid: 1 },
       "post /wsapi/auth_with_assertion invalid": { success: false },
       "post /wsapi/auth_with_assertion ajaxError": undefined,
       "post /wsapi/cert_key valid": random_cert,
@@ -141,7 +141,7 @@ BrowserID.Mocks.xhr = (function() {
       };
 
 
-      if(type === "post" && !obj.data.csrf) {
+      if(type === "post" && obj.data.indexOf("csrf") === -1) {
         ok(false, "missing csrf token on POST request");
       }
 
diff --git a/resources/static/test/testHelpers/helpers.js b/resources/static/test/testHelpers/helpers.js
index 493ea1db0756779b69735a7f18c104b51c1d3e26..31ac2d086649287fd4b4d68810fed152a9cdeab7 100644
--- a/resources/static/test/testHelpers/helpers.js
+++ b/resources/static/test/testHelpers/helpers.js
@@ -47,6 +47,12 @@ BrowserID.TestHelpers = (function() {
     ok($("#error #network").text().length, "network contents have been written");
   }
 
+  function clearStorage() {
+    for(var key in localStorage) {
+      localStorage.removeItem(key);
+    }
+  }
+
   var TestHelpers = {
     XHR_TIME_UNTIL_DELAY: 100,
     setup: function() {
@@ -63,7 +69,7 @@ BrowserID.TestHelpers = (function() {
       transport.useResult("valid");
 
       network.init();
-      storage.clear();
+      clearStorage();
 
       $("body").stop().show();
       $("body")[0].className = "";
@@ -93,7 +99,7 @@ BrowserID.TestHelpers = (function() {
         time_until_delay: 10 * 1000
       });
       network.init();
-      storage.clear();
+      clearStorage();
       screens.wait.hide();
       screens.error.hide();
       screens.delay.hide();
diff --git a/resources/views/communication_iframe.ejs b/resources/views/communication_iframe.ejs
index 5a1e2c07fe3562992a5f3c3fbc8ce53630a4884a..147b0228a8e6a400f841f2cbadbf60459ef7823b 100644
--- a/resources/views/communication_iframe.ejs
+++ b/resources/views/communication_iframe.ejs
@@ -5,24 +5,7 @@
 <html>
 <head><title>non-interactive iframe</title>
   <meta charset="utf-8">
-  <% if(production) { %>
-    <script type="text/javascript" src="/production/communication_iframe.js"></script>
-  <% } else { %>
-    <script type="text/javascript" src="/lib/jquery-1.7.1.min.js"></script>
-    <script type="text/javascript" src="/lib/jschannel.js"></script>
-    <script type="text/javascript" src="/lib/underscore-min.js"></script>
-    <script type="text/javascript" src="/lib/vepbundle.js"></script>
-    <script type="text/javascript" src="/lib/hub.js"></script>
-    <script type="text/javascript" src="/shared/javascript-extensions.js"></script>
-    <script type="text/javascript" src="/shared/browserid.js"></script>
-    <script type="text/javascript" src="/shared/mediator.js"></script>
-    <script type="text/javascript" src="/shared/helpers.js"></script>
-    <script type="text/javascript" src="/shared/storage.js"></script>
-    <script type="text/javascript" src="/shared/xhr.js"></script>
-    <script type="text/javascript" src="/shared/network.js"></script>
-    <script type="text/javascript" src="/shared/user.js"></script>
-    <script type="text/javascript" src="/communication_iframe/start.js"></script>
-  <% } %>
+  <%- cachify_js('/production/communication_iframe.js') %>
 </head>
 <body></body>
 </html>
diff --git a/resources/views/index.ejs b/resources/views/index.ejs
index 57f1c8d7b96254292d735fb74bf6bab32639dd4a..6b1f49aec9e17322ae8d9f2f5d9c3ce2d079850c 100644
--- a/resources/views/index.ejs
+++ b/resources/views/index.ejs
@@ -21,6 +21,16 @@
             </ul>
           </section>
 
+          <section id="logout_everywhere">
+            <header class="cf buttonrow">
+                <h2>Logout from all websites</h2>
+                <div class="completion_text"> Logged Out! </div>
+                <button class="logout_everywhere">logout</button>
+            </header>
+
+            <div class="email">You can sign in again by entering your password.  None of your website accounts will be lost.</div></li>
+          </section>
+
           <section id="edit_password">
             <header class="buttonrow cf">
               <h2>Password</h2>
diff --git a/resources/views/test.ejs b/resources/views/test.ejs
index ce0c52d747d9715261ccf0d15fc0cf9747544f2d..dc0bbea64df28b06d94f65f432e8bd552e9c395b 100644
--- a/resources/views/test.ejs
+++ b/resources/views/test.ejs
@@ -125,6 +125,7 @@
     <script src="/dialog/controllers/email_chosen.js"></script>
     <script src="/dialog/controllers/provision_primary_user.js"></script>
     <script src="/dialog/controllers/primary_user_provisioned.js"></script>
+    <script src="/dialog/controllers/is_this_your_computer.js"></script>
 
     <script src="/pages/page_helpers.js"></script>
     <script src="/pages/add_email_address.js"></script>
@@ -183,6 +184,7 @@
     <script src="cases/controllers/email_chosen.js"></script>
     <script src="cases/controllers/provision_primary_user.js"></script>
     <script src="cases/controllers/primary_user_provisioned.js"></script>
+    <script src="cases/controllers/is_this_your_computer.js"></script>
 
     <!-- must go last or all other tests will fail. -->
     <script src="cases/controllers/dialog.js"></script>
diff --git a/tests/auth-with-assertion-test.js b/tests/auth-with-assertion-test.js
index 8784afce7d48a987dd596be63308827f32b50c65..eda9f1768df9931dbebd5c39817f1ef2fbd997ed 100755
--- a/tests/auth-with-assertion-test.js
+++ b/tests/auth-with-assertion-test.js
@@ -48,8 +48,8 @@ suite.addBatch({
     "and logging in with the assertion succeeds": {
       topic: function(assertion)  {
         wsapi.post('/wsapi/auth_with_assertion', {
-          email: TEST_EMAIL,
-          assertion: assertion
+          assertion: assertion,
+          ephemeral: true
         }).call(this);
       },
       "works": function(err, r) {
diff --git a/tests/cert-emails-test.js b/tests/cert-emails-test.js
index d58dea80a63c7fe6f2e1e16a95168febe59f5e38..1262ffefa2b89fb66c8358219d10afe14e17fc57 100755
--- a/tests/cert-emails-test.js
+++ b/tests/cert-emails-test.js
@@ -12,9 +12,7 @@ start_stop = require('./lib/start-stop.js'),
 wsapi = require('./lib/wsapi.js'),
 email = require('../lib/email.js'),
 ca = require('../lib/keysigner/ca.js'),
-jwcert = require('jwcrypto/jwcert'),
 jwk = require('jwcrypto/jwk'),
-jws = require('jwcrypto/jws'),
 jwt = require('jwcrypto/jwt');
 
 var suite = vows.describe('cert-emails');
@@ -98,7 +96,11 @@ suite.addBatch({
     }
   },
   "cert key invoked with proper argument": {
-    topic: wsapi.post(cert_key_url, { email: 'syncer@somehost.com', pubkey: kp.publicKey.serialize() }),
+    topic: wsapi.post(cert_key_url, {
+      email: 'syncer@somehost.com',
+      pubkey: kp.publicKey.serialize(),
+      ephemeral: false
+    }),
     "returns a response with a proper content-type" : function(err, r) {
       assert.strictEqual(r.code, 200);
     },
@@ -143,7 +145,11 @@ suite.addBatch({
     }
   },
   "cert key invoked proper arguments but incorrect email address": {
-    topic: wsapi.post(cert_key_url, { email: 'syncer2@somehost.com', pubkey: kp.publicKey.serialize() }),
+    topic: wsapi.post(cert_key_url, {
+      email: 'syncer2@somehost.com',
+      pubkey: kp.publicKey.serialize(),
+      ephemeral: false
+    }),
     "returns a response with a proper error content-type" : function(err, r) {
       assert.strictEqual(r.code, 400);
     }
diff --git a/tests/delegated-primary-test.js b/tests/delegated-primary-test.js
old mode 100644
new mode 100755
index a9a286a231c8866d6a9ff8cc2f4a5acfbe6a0b22..94818c231b8c280c583346190f519e7c960ae3a3
--- a/tests/delegated-primary-test.js
+++ b/tests/delegated-primary-test.js
@@ -1,3 +1,9 @@
+#!/usr/bin/env node
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 require('./lib/test_env.js');
 
 const
diff --git a/tests/forgotten-email-test.js b/tests/forgotten-email-test.js
index ed0fbc8ce9801f0e03ce04a9cd88bac833e5425e..9fc0bc43025b0ecf91e422d7d08c266fce6bb731 100755
--- a/tests/forgotten-email-test.js
+++ b/tests/forgotten-email-test.js
@@ -162,13 +162,21 @@ suite.addBatch({
 // valid (this is so *until* someone clicks through)
 suite.addBatch({
   "first email works": {
-    topic: wsapi.post('/wsapi/authenticate_user', { email: 'first@fakeemail.com', pass: 'firstfakepass' }),
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: 'first@fakeemail.com',
+      pass: 'firstfakepass',
+      ephemeral: false
+    }),
     "should work": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, true);
     }
   },
   "second email works": {
-    topic: wsapi.post('/wsapi/authenticate_user', { email: 'second@fakeemail.com', pass: 'firstfakepass' }),
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: 'second@fakeemail.com',
+      pass: 'firstfakepass',
+      ephemeral: false
+    }),
     "should work": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, true);
     }
@@ -192,13 +200,21 @@ suite.addBatch({
 // password, and all other combinations should fail
 suite.addBatch({
   "first email, first pass bad": {
-    topic: wsapi.post('/wsapi/authenticate_user', { email: 'first@fakeemail.com', pass: 'firstfakepass' }),
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: 'first@fakeemail.com',
+      pass: 'firstfakepass',
+      ephemeral: false
+    }),
     "shouldn't work": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, false);
     }
   },
   "first email, second pass good": {
-    topic: wsapi.post('/wsapi/authenticate_user', { email: 'first@fakeemail.com', pass: 'secondfakepass' }),
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: 'first@fakeemail.com',
+      pass: 'secondfakepass',
+      ephemeral: false
+    }),
     "should work": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, true);
     }
@@ -210,13 +226,21 @@ suite.addBatch({
     }
   },
   "second email, first pass good": {
-    topic: wsapi.post('/wsapi/authenticate_user', { email: 'second@fakeemail.com', pass: 'firstfakepass' }),
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: 'second@fakeemail.com',
+      pass: 'firstfakepass',
+      ephemeral: false
+    }),
     "should work": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, true);
     }
   },
   "second email, second pass bad": {
-    topic: wsapi.post('/wsapi/authenticate_user', { email: 'second@fakeemail.com', pass: 'secondfakepass' }),
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: 'second@fakeemail.com',
+      pass: 'secondfakepass',
+      ephemeral: false
+    }),
     "shouldn' work": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, false);
     }
diff --git a/tests/lib/wsapi.js b/tests/lib/wsapi.js
index 0bfd9989cce0914e027b4603a3cfdf0a61a73c1b..868c86d02756fd53ed624cbe543b403355cb510e 100644
--- a/tests/lib/wsapi.js
+++ b/tests/lib/wsapi.js
@@ -21,6 +21,10 @@ exports.injectCookies = function(cookies) {
   wcli.injectCookies({cookieJar: cookies}, context);
 };
 
+exports.getCookie = function(which) {
+  return wcli.getCookie(context, which);
+};
+
 exports.get = function (path, getArgs) {
   return function () {
     wcli.get(configuration, path, context, getArgs, this.callback);
diff --git a/tests/password-bcrypt-update-test.js b/tests/password-bcrypt-update-test.js
index c403d6ef715d1250b6671128abd9a64b1b7af923..51d9660810960fc4a55c95ec269e5be901dc9ed4 100755
--- a/tests/password-bcrypt-update-test.js
+++ b/tests/password-bcrypt-update-test.js
@@ -117,7 +117,8 @@ suite.addBatch({
   "re-authentication": {
     topic: wsapi.post('/wsapi/authenticate_user', {
       email: TEST_EMAIL,
-      pass: TEST_PASSWORD
+      pass: TEST_PASSWORD,
+      ephemeral: false
     }),
     "should work": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, true);
@@ -153,7 +154,8 @@ suite.addBatch({
   "and re-authentication": {
     topic: wsapi.post('/wsapi/authenticate_user', {
       email: TEST_EMAIL,
-      pass: TEST_PASSWORD
+      pass: TEST_PASSWORD,
+      ephemeral: false
     }),
     "should still work": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, true);
diff --git a/tests/password-length-test.js b/tests/password-length-test.js
index 2c956b1a1313a8a46f94718b0860364b6314f29f..eb33773510aaf29e298d7eb8364e0bf98774c5c9 100755
--- a/tests/password-length-test.js
+++ b/tests/password-length-test.js
@@ -20,11 +20,7 @@ suite.options.error = false;
 
 start_stop.addStartupBatches(suite);
 
-// surpress console output of emails with a noop email intercepto
 var token = undefined;
-start_stop.browserid.on('token', function(secret) {
-  token = secret;
-});
 
 suite.addBatch({
   "get csrf token": {
@@ -52,33 +48,53 @@ suite.addBatch({
   }
 });
 
+// wait for the token
+suite.addBatch({
+  "a token": {
+    topic: function() {
+      start_stop.waitForToken(this.callback);
+    },
+    "is obtained": function (t) {
+      assert.strictEqual(typeof t, 'string');
+      token = t;
+    }
+  }
+});
+
+
 // create a new account via the api with (first address)
 suite.addBatch({
   "a password that is too short": {
-    topic: wsapi.post('/wsapi/complete_user_creation', {
-      token: token,
-      pass: '0123456' // less than 8 chars, invalid
-    }),
+    topic: function() {
+      wsapi.post('/wsapi/complete_user_creation', {
+        token: token,
+        pass: '0123456' // less than 8 chars, invalid
+      }).call(this)
+    },
     "causes a HTTP error response": function(err, r) {
       assert.equal(r.code, 400);
       assert.equal(r.body, "Bad Request: valid passwords are between 8 and 80 chars");
     }
   },
   "a password that is too long": {
-    topic: wsapi.post('/wsapi/complete_user_creation', {
-      token: token,
-      pass: '012345678901234567890123456789012345678901234567890123456789012345678901234567891', // more than 81 chars, invalid.
-    }),
+    topic: function() {
+      wsapi.post('/wsapi/complete_user_creation', {
+        token: token,
+        pass: '012345678901234567890123456789012345678901234567890123456789012345678901234567891', // more than 81 chars, invalid.
+      }).call(this);
+    },
     "causes a HTTP error response": function(err, r) {
       assert.equal(r.code, 400);
       assert.equal(r.body, "Bad Request: valid passwords are between 8 and 80 chars");
     }
   },
   "but a password that is just right": {
-    topic: wsapi.post('/wsapi/complete_user_creation', {
-      token: token,
-      pass: 'ahhh.  this is just right.'
-    }),
+    topic: function() {
+      wsapi.post('/wsapi/complete_user_creation', {
+        token: token,
+        pass: 'ahhh.  this is just right.'
+      }).call(this);
+    },
     "works just fine": function(err, r) {
       assert.equal(r.code, 200);
     }
diff --git a/tests/password-update-test.js b/tests/password-update-test.js
index 9d484f56f47025c365bedee2af4a39fac4caf7eb..a8cbf9fc9320c9c59438f75e8fa210129db07aa2 100755
--- a/tests/password-update-test.js
+++ b/tests/password-update-test.js
@@ -74,7 +74,8 @@ suite.addBatch({
   "authenticating with the password": {
     topic: wsapi.post('/wsapi/authenticate_user', {
       email: TEST_EMAIL,
-      pass: OLD_PASSWORD
+      pass: OLD_PASSWORD,
+      ephemeral: false
     }),
     "works as expected": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, true);
@@ -83,7 +84,8 @@ suite.addBatch({
   "authenticating with the wrong password": {
     topic: wsapi.post('/wsapi/authenticate_user', {
       email: TEST_EMAIL,
-      pass: NEW_PASSWORD
+      pass: NEW_PASSWORD,
+      ephemeral: false
     }),
     "fails as expected": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, false);
@@ -131,7 +133,8 @@ suite.addBatch({
   "authenticating with the password": {
     topic: wsapi.post('/wsapi/authenticate_user', {
       email: TEST_EMAIL,
-      pass: NEW_PASSWORD
+      pass: NEW_PASSWORD,
+      ephemeral: false
     }),
     "works as expected": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, true);
@@ -140,7 +143,8 @@ suite.addBatch({
   "authenticating with the wrong password": {
     topic: wsapi.post('/wsapi/authenticate_user', {
       email: TEST_EMAIL,
-      pass: OLD_PASSWORD
+      pass: OLD_PASSWORD,
+      ephemeral: false
     }),
     "fails as expected": function(err, r) {
       assert.strictEqual(JSON.parse(r.body).success, false);
diff --git a/tests/primary-then-secondary-test.js b/tests/primary-then-secondary-test.js
index 6b47385a950056d788ba6c144def02c805cacb05..6720802ce321229428541d86d045793d21567c3d 100755
--- a/tests/primary-then-secondary-test.js
+++ b/tests/primary-then-secondary-test.js
@@ -50,8 +50,8 @@ suite.addBatch({
     "and logging in with the assertion succeeds": {
       topic: function(assertion)  {
         wsapi.post('/wsapi/auth_with_assertion', {
-          email: TEST_EMAIL,
-          assertion: assertion
+          assertion: assertion,
+          ephemeral: true
         }).call(this);
       },
       "works": function(err, r) {
@@ -198,7 +198,8 @@ suite.addBatch({
   "authentication with first email": {
     topic: wsapi.post('/wsapi/authenticate_user', {
       email: TEST_EMAIL,
-      pass: TEST_PASS
+      pass: TEST_PASS,
+      ephemeral: false
     }),
     "works": function(err, r) {
       assert.strictEqual(r.code, 200);
@@ -207,7 +208,8 @@ suite.addBatch({
   "authentication with second email": {
     topic: wsapi.post('/wsapi/authenticate_user', {
       email: SECONDARY_EMAIL,
-      pass: TEST_PASS
+      pass: TEST_PASS,
+      ephemeral: false
     }),
     "works": function(err, r) {
       assert.strictEqual(r.code, 200);
diff --git a/tests/registration-status-wsapi-test.js b/tests/registration-status-wsapi-test.js
index 394383c207dabeb1b78e1a924d4f91ec55064c37..f4515775c3d4e8789c18045abec75813ce43e3e4 100755
--- a/tests/registration-status-wsapi-test.js
+++ b/tests/registration-status-wsapi-test.js
@@ -35,7 +35,11 @@ suite.addBatch({
 
 suite.addBatch({
   "authentication as an unknown user": {
-    topic: wsapi.post('/wsapi/authenticate_user', { email: 'first@fakeemail.com', pass: 'secondfakepass' }),
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: 'first@fakeemail.com',
+      pass: 'secondfakepass',
+      ephemeral: false
+    }),
     "fails": function (err, r) {
       assert.isFalse(JSON.parse(r.body).success);
     }
@@ -234,7 +238,11 @@ suite.addBatch({
 
 suite.addBatch({
   "after re-registration, authenticating with new credetials": {
-    topic: wsapi.post('/wsapi/authenticate_user', { email: 'first@fakeemail.com', pass: 'secondfakepass' }),
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: 'first@fakeemail.com',
+      pass: 'secondfakepass',
+      ephemeral: false
+    }),
     "works as you might expect": function (err, r) {
       assert.strictEqual(JSON.parse(r.body).success, true);
     }
diff --git a/tests/session-context-test.js b/tests/session-context-test.js
new file mode 100755
index 0000000000000000000000000000000000000000..563042f5024109dc9dc9069cb201be308fbd951f
--- /dev/null
+++ b/tests/session-context-test.js
@@ -0,0 +1,107 @@
+#!/usr/bin/env node
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+require('./lib/test_env.js');
+
+const assert =
+require('assert'),
+vows = require('vows'),
+start_stop = require('./lib/start-stop.js'),
+wsapi = require('./lib/wsapi.js'),
+db = require('../lib/db.js'),
+config = require('../lib/configuration.js'),
+bcrypt = require('bcrypt');
+
+var suite = vows.describe('session-context');
+
+// disable vows (often flakey?) async error behavior
+suite.options.error = false;
+
+start_stop.addStartupBatches(suite);
+
+const TEST_EMAIL = 'someuser@somedomain.com',
+      PASSWORD = 'thisismypassword';
+
+var token = undefined;
+
+// first stage the account
+suite.addBatch({
+  "account staging": {
+    topic: wsapi.post('/wsapi/stage_user', {
+      email: TEST_EMAIL,
+      site: 'fakesite.com'
+    }),
+    "works":     function(err, r) {
+      assert.equal(r.code, 200);
+    }
+  }
+});
+
+// wait for the token
+suite.addBatch({
+  "a token": {
+    topic: function() {
+      start_stop.waitForToken(this.callback);
+    },
+    "is obtained": function (t) {
+      assert.strictEqual(typeof t, 'string');
+      token = t;
+    }
+  }
+});
+
+// create a new account via the api with (first address)
+suite.addBatch({
+  "setting password": {
+    topic: function() {
+      wsapi.post('/wsapi/complete_user_creation', {
+        token: token,
+        pass: PASSWORD
+      }).call(this);
+    },
+    "works just fine": function(err, r) {
+      assert.equal(r.code, 200);
+    }
+  }
+});
+
+suite.addBatch({
+  "authenticating with the password": {
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: TEST_EMAIL,
+      pass: PASSWORD,
+      ephemeral: true
+    }),
+    "works as expected": function(err, r) {
+      assert.strictEqual(JSON.parse(r.body).success, true);
+    }
+  }
+});
+
+suite.addBatch({
+  "session context": {
+    topic: wsapi.get('/wsapi/session_context'),
+    "contains values expected": function(err, r) {
+      assert.isNull(err);
+      var resp = JSON.parse(r.body);
+      assert.strictEqual(typeof resp.csrf_token, 'string');
+      var serverTime = new Date(resp.server_time);
+      assert.ok(new Date() - serverTime < 5000);      
+      assert.strictEqual(resp.authenticated, true);
+      assert.strictEqual(resp.auth_level, 'password');
+      var domainKeyCreation = new Date(resp.domain_key_creation_time);
+      assert.ok(new Date() - serverTime < 365 * 24 * 60 * 60 * 1000);
+      assert.strictEqual(typeof resp.random_seed, 'string');
+      assert.strictEqual(resp.userid, 1);
+    }
+  }
+});
+
+start_stop.addShutdownBatches(suite);
+
+// run or export the suite.
+if (process.argv[1] === __filename) suite.run();
+else suite.export(module);
diff --git a/tests/session-duration-test.js b/tests/session-duration-test.js
new file mode 100755
index 0000000000000000000000000000000000000000..0e5fdd8257f01b037ca5ff355161534af447611d
--- /dev/null
+++ b/tests/session-duration-test.js
@@ -0,0 +1,233 @@
+#!/usr/bin/env node
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+require('./lib/test_env.js');
+
+const assert =
+require('assert'),
+vows = require('vows'),
+start_stop = require('./lib/start-stop.js'),
+wsapi = require('./lib/wsapi.js'),
+db = require('../lib/db.js'),
+config = require('../lib/configuration.js'),
+bcrypt = require('bcrypt'),
+primary = require('./lib/primary.js'),
+ca = require('../lib/keysigner/ca.js'),
+jwk = require('jwcrypto/jwk'),
+jwt = require('jwcrypto/jwt'),
+jws = require('jwcrypto/jws');
+
+var suite = vows.describe('session-context');
+
+// disable vows (often flakey?) async error behavior
+suite.options.error = false;
+
+start_stop.addStartupBatches(suite);
+
+// test that auth_with_assertion also respects the 'ephemeral' argument
+const PRIMARY_DOMAIN = 'example.domain',
+      PRIMARY_EMAIL = 'testuser@' + PRIMARY_DOMAIN,
+      PRIMARY_ORIGIN = 'http://127.0.0.1:10002';
+
+// here we go!  let's authenticate with an assertion from
+// a primary.
+
+var primaryUser = new primary({
+  email: PRIMARY_EMAIL,
+  domain: PRIMARY_DOMAIN
+});
+
+suite.addBatch({
+  "generating an assertion": {
+    topic: function() {
+      return primaryUser.getAssertion(PRIMARY_ORIGIN);
+    },
+    "succeeds": function(r, err) {
+      assert.isString(r);
+    },
+    "and logging in with the assertion with ephemeral = true": {
+      topic: function(assertion)  {
+        wsapi.post('/wsapi/auth_with_assertion', {
+          assertion: assertion,
+          ephemeral: true
+        }).call(this);
+      },
+      "works": function(err, r) {
+        var resp = JSON.parse(r.body);
+        assert.isObject(resp);
+        assert.isTrue(resp.success);
+      },
+      "has expected duration": function(err, r) {
+        assert.strictEqual(parseInt(wsapi.getCookie(/^browserid_state/).split('.')[3], 10), config.get('ephemeral_session_duration_ms'));
+      }
+    }
+  }
+});
+
+suite.addBatch({
+  "generating an assertion": {
+    topic: function() {
+      return primaryUser.getAssertion(PRIMARY_ORIGIN);
+    },
+    "succeeds": function(r, err) {
+      assert.isString(r);
+    },
+    "and logging in with the assertion with ephemeral = false": {
+      topic: function(assertion)  {
+        wsapi.post('/wsapi/auth_with_assertion', {
+          assertion: assertion,
+          ephemeral: false
+        }).call(this);
+      },
+      "works": function(err, r) {
+        var resp = JSON.parse(r.body);
+        assert.isObject(resp);
+        assert.isTrue(resp.success);
+      },
+      "has expected duration": function(err, r) {
+        assert.strictEqual(parseInt(wsapi.getCookie(/^browserid_state/).split('.')[3], 10), config.get('authentication_duration_ms'));
+      }
+    }
+  }
+});
+
+// now test that authenticate_user & secondary emails properly respect the 'ephemeral' argument to
+// alter session length
+const TEST_EMAIL = 'someuser@somedomain.com',
+      PASSWORD = 'thisismypassword';
+
+var token = undefined;
+
+// first stage the account
+suite.addBatch({
+  "account staging": {
+    topic: wsapi.post('/wsapi/stage_user', {
+      email: TEST_EMAIL,
+      site: 'fakesite.com'
+    }),
+    "works":     function(err, r) {
+      assert.equal(r.code, 200);
+    }
+  }
+});
+
+// wait for the token
+suite.addBatch({
+  "a token": {
+    topic: function() {
+      start_stop.waitForToken(this.callback);
+    },
+    "is obtained": function (t) {
+      assert.strictEqual(typeof t, 'string');
+      token = t;
+    }
+  }
+});
+
+// create a new account via the api with (first address)
+suite.addBatch({
+  "setting password": {
+    topic: function() {
+      wsapi.post('/wsapi/complete_user_creation', {
+        token: token,
+        pass: PASSWORD
+      }).call(this);
+    },
+    "works just fine": function(err, r) {
+      assert.equal(r.code, 200);
+    }
+  }
+});
+
+suite.addBatch({
+  "authenticating with the password and ephemeral = true": {
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: TEST_EMAIL,
+      pass: PASSWORD,
+      ephemeral: true
+    }),
+    "works as expected": function(err, r) {
+      assert.strictEqual(JSON.parse(r.body).success, true);
+    },
+    "yields a session of expected length": function(err, r) {
+      assert.strictEqual(parseInt(wsapi.getCookie(/^browserid_state/).split('.')[3], 10), config.get('ephemeral_session_duration_ms'));
+    }
+  }
+});
+
+suite.addBatch({
+  "authenticating with the password and ephemeral = false": {
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: TEST_EMAIL,
+      pass: PASSWORD,
+      ephemeral: false
+    }),
+    "works as expected": function(err, r) {
+      assert.strictEqual(JSON.parse(r.body).success, true);
+    },
+    "yields a session of expected length": function(err, r) {
+      assert.strictEqual(parseInt(wsapi.getCookie(/^browserid_state/).split('.')[3], 10), config.get('authentication_duration_ms'));
+    }
+  }
+});
+
+// finally, let's verify that ephemeral is properly handled when certifying keys for a user
+
+var kp = jwk.KeyPair.generate("RS", 64);
+
+suite.addBatch({
+  "cert_key invoked with ephemeral = false": {
+    topic: wsapi.post('/wsapi/cert_key', {
+      email: TEST_EMAIL,
+      pubkey: kp.publicKey.serialize(),
+      ephemeral: false
+    }),
+    "returns a response with a proper content-type" : function(err, r) {
+      assert.strictEqual(r.code, 200);
+    },
+    "returns a valid cert": function(err, r) {
+      ca.verifyChain('127.0.0.1', [r.body], function(pk) {
+        assert.isTrue(kp.publicKey.equals(pk));
+      });
+    },
+    "has the correct expiration": function(err, r) {
+      var cert = new jws.JWS();
+      cert.parse(r.body);
+      var pl = JSON.parse(cert.payload);
+      assert.strictEqual(pl.exp - pl.iat, config.get('certificate_validity_ms'));
+    }
+  }
+});
+
+suite.addBatch({
+  "cert_key invoked with ephemeral = true": {
+    topic: wsapi.post('/wsapi/cert_key', {
+      email: TEST_EMAIL,
+      pubkey: kp.publicKey.serialize(),
+      ephemeral: true
+    }),
+    "returns a response with a proper content-type" : function(err, r) {
+      assert.strictEqual(r.code, 200);
+    },
+    "returns a valid cert": function(err, r) {
+      ca.verifyChain('127.0.0.1', [r.body], function(pk) {
+        assert.isTrue(kp.publicKey.equals(pk));
+      });
+    },
+    "has the correct expiration": function(err, r) {
+      var cert = new jws.JWS();
+      cert.parse(r.body);
+      var pl = JSON.parse(cert.payload);
+      assert.strictEqual(pl.exp - pl.iat, config.get('ephemeral_session_duration_ms'));
+    }
+  }
+});
+
+start_stop.addShutdownBatches(suite);
+
+// run or export the suite.
+if (process.argv[1] === __filename) suite.run();
+else suite.export(module);
diff --git a/tests/session-prolong-test.js b/tests/session-prolong-test.js
new file mode 100755
index 0000000000000000000000000000000000000000..16b0ef8d4431a88fe6d5194e6605c9b831e1a5d3
--- /dev/null
+++ b/tests/session-prolong-test.js
@@ -0,0 +1,119 @@
+#!/usr/bin/env node
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+require('./lib/test_env.js');
+
+const assert =
+require('assert'),
+vows = require('vows'),
+start_stop = require('./lib/start-stop.js'),
+wsapi = require('./lib/wsapi.js'),
+db = require('../lib/db.js'),
+config = require('../lib/configuration.js'),
+bcrypt = require('bcrypt');
+
+var suite = vows.describe('session-prolong');
+
+// disable vows (often flakey?) async error behavior
+suite.options.error = false;
+
+start_stop.addStartupBatches(suite);
+
+const TEST_EMAIL = 'someuser@somedomain.com',
+      PASSWORD = 'thisismypassword';
+
+var token = undefined;
+
+// first stage the account
+suite.addBatch({
+  "account staging": {
+    topic: wsapi.post('/wsapi/stage_user', {
+      email: TEST_EMAIL,
+      site: 'fakesite.com'
+    }),
+    "works":     function(err, r) {
+      assert.equal(r.code, 200);
+    }
+  }
+});
+
+// wait for the token
+suite.addBatch({
+  "a token": {
+    topic: function() {
+      start_stop.waitForToken(this.callback);
+    },
+    "is obtained": function (t) {
+      assert.strictEqual(typeof t, 'string');
+      token = t;
+    }
+  }
+});
+
+// create a new account via the api with (first address)
+suite.addBatch({
+  "setting password": {
+    topic: function() {
+      wsapi.post('/wsapi/complete_user_creation', {
+        token: token,
+        pass: PASSWORD
+      }).call(this);
+    },
+    "works just fine": function(err, r) {
+      assert.equal(r.code, 200);
+    }
+  }
+});
+
+suite.addBatch({
+  "authenticating with the password": {
+    topic: wsapi.post('/wsapi/authenticate_user', {
+      email: TEST_EMAIL,
+      pass: PASSWORD,
+      ephemeral: true
+    }),
+    "works as expected": function(err, r) {
+      assert.strictEqual(JSON.parse(r.body).success, true);
+    }
+  }
+});
+
+suite.addBatch({
+  "session length": {
+    topic: function() {
+      this.callback(wsapi.getCookie(/^browserid_state/));
+    },
+    "is short (ephemeral)": function(cookie) {
+      assert.equal(cookie.split('.')[3], config.get('ephemeral_session_duration_ms'));
+    }
+  }
+});
+
+suite.addBatch({
+  "session prolonging": {
+    topic: wsapi.post('/wsapi/prolong_session', {}),
+    "returns 200": function(err, r) {
+      assert.strictEqual(r.code, 200);
+    }
+  }
+});
+
+suite.addBatch({
+  "session length": {
+    topic: function() {
+      this.callback(wsapi.getCookie(/^browserid_state/));
+    },
+    "becomes long": function(cookie) {
+      assert.equal(cookie.split('.')[3], config.get('authentication_duration_ms'));
+    }
+  }
+});
+
+start_stop.addShutdownBatches(suite);
+
+// run or export the suite.
+if (process.argv[1] === __filename) suite.run();
+else suite.export(module);
diff --git a/tests/stalled-mysql-test.js b/tests/stalled-mysql-test.js
index cd40adba4c379ee97fa585626ba816ff46eb6cd7..fc37017c3f888c00bb90e1cd4b98a72748be3fb6 100755
--- a/tests/stalled-mysql-test.js
+++ b/tests/stalled-mysql-test.js
@@ -112,7 +112,8 @@ suite.addBatch({
   "authenticate_user": {
     topic: wsapi.post('/wsapi/authenticate_user', {
       email: 'test@example.com',
-      pass: 'oogabooga'
+      pass: 'oogabooga',
+      ephemeral: false
     }),
     "fails with 503": function(err, r) {
       assert.strictEqual(r.code, 503);
@@ -227,7 +228,8 @@ suite.addBatch({
   "cert_key": {
     topic: wsapi.post('/wsapi/cert_key', {
       email: "test@whatev.er",
-      pubkey: "bogus"
+      pubkey: "bogus",
+      ephemeral: false
     }),
     "fails with 503": function(err, r) {
       assert.strictEqual(r.code, 503);
@@ -364,7 +366,8 @@ suite.addBatch({
   "auth_with_assertion": {
     topic: function() {
       wsapi.post('/wsapi/auth_with_assertion', {
-        assertion: g_assertion
+        assertion: g_assertion,
+        ephemeral: true
       }).call(this);
     },
     "fails with 503": function(err, r) {
diff --git a/tests/two-level-auth-test.js b/tests/two-level-auth-test.js
index d73c3c812e9b78d5e3b6ca1e641fc9ba0b0e06c9..0353d89acc9c5b91cfdbddcfabffeb55b161292a 100755
--- a/tests/two-level-auth-test.js
+++ b/tests/two-level-auth-test.js
@@ -41,8 +41,8 @@ suite.addBatch({
     "and logging in with the assertion": {
       topic: function(assertion)  {
         wsapi.post('/wsapi/auth_with_assertion', {
-          email: TEST_EMAIL,
-          assertion: assertion
+          assertion: assertion,
+          ephemeral: true
         }).call(this);
       },
       "succeeds": function(err, r) {
diff --git a/tests/verifier-test.js b/tests/verifier-test.js
index b2a455dc04807502630199c49333d5a9a42e7572..499855873f1eb3250de9603b7d79c8f390b71f60 100755
--- a/tests/verifier-test.js
+++ b/tests/verifier-test.js
@@ -97,7 +97,8 @@ suite.addBatch({
     topic: function() {
       wsapi.post('/wsapi/cert_key', {
         email: TEST_EMAIL,
-        pubkey: g_keypair.publicKey.serialize()
+        pubkey: g_keypair.publicKey.serialize(),
+        ephemeral: false
       }).call(this);
     },
     "works swimmingly": function(err, r) {