diff --git a/resources/static/dialog/controllers/required_email.js b/resources/static/dialog/controllers/required_email.js
index 1715578d451c8eb465ff64d560c9975db8803c2c..205c1df7650415dfcbfe2e91e1cad0a6a8aa4dd6 100644
--- a/resources/static/dialog/controllers/required_email.js
+++ b/resources/static/dialog/controllers/required_email.js
@@ -127,6 +127,12 @@ BrowserID.Modules.RequiredEmail = (function() {
           });
           ready();
         }
+        else if(emailInfo && emailInfo.cert) {
+          // primary user with valid cert, user can sign in normally.
+          primaryInfo = emailInfo;
+          showTemplate({ signin: true, primary: true });
+          ready();
+        }
         else {
           // At this point, there are several possibilities:
           // 1) Authenticated primary user who has valid cert.
@@ -136,9 +142,7 @@ BrowserID.Modules.RequiredEmail = (function() {
           user.addressInfo(email, function(info) {
             if(info.type === "primary") primaryInfo = info;
 
-            if (info.authed || (emailInfo && emailInfo.cert)) {
-              // primary user with valid cert, user can sign in normally.
-              // OR
+            if (info.authed) {
               // this is a primary user who is authenticated with their IdP.
               // We know the user has control of this address, give them
               // a chance to hit "sign in" before we kick them off to the
diff --git a/resources/static/dialog/resources/state_machine.js b/resources/static/dialog/resources/state_machine.js
index 258a5e5ddedcaae47f8fc4d34eb34eed75e1a421..76717c5068e974b54091248130f413ff4b49d9e9 100644
--- a/resources/static/dialog/resources/state_machine.js
+++ b/resources/static/dialog/resources/state_machine.js
@@ -143,7 +143,13 @@
       // 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);
+      var idInfo = storage.getEmail(email);
+      if(idInfo && idInfo.cert) {
+        mediator.publish("primary_user_ready", info);
+      }
+      else {
+        startState(false, "doProvisionPrimaryUser", info);
+      }
     });
 
     subscribe("primary_user_provisioned", function(msg, info) {
diff --git a/resources/static/pages/page_helpers.js b/resources/static/pages/page_helpers.js
index 06d29ce4c31426bc02d9b5bcb4801d9d4888408c..7121f70ed998b9ac35e7ffed7d4969b998afc2ff 100644
--- a/resources/static/pages/page_helpers.js
+++ b/resources/static/pages/page_helpers.js
@@ -9,6 +9,7 @@ BrowserID.PageHelpers = (function() {
   var win = window,
       locStorage = win.localStorage,
       bid = BrowserID,
+      user = bid.User,
       helpers = bid.Helpers,
       dom = bid.DOM,
       errorDisplay = bid.ErrorDisplay,
@@ -137,7 +138,16 @@ BrowserID.PageHelpers = (function() {
       relay_url: "https://browserid.org/relay",
       window_features: "width=700,height=375",
       params: url
-    }, callback);
+    }, function(error, result) {
+      // We have to force a reset of the primary caches because the user's
+      // authentication status may be incorrect.
+      // XXX a better solution here would be to change the authentication
+      // status of the user inside of the cache.
+      if(!error) {
+        user.resetCaches();
+      }
+      callback && callback(error, result);
+    });
   }
 
   return {
diff --git a/resources/static/pages/signin.js b/resources/static/pages/signin.js
index fdf47f34ad689de484c0a0a705f49b0526d8d14e..27633981de20533c67c13fd6bf90c66aecd8a159 100644
--- a/resources/static/pages/signin.js
+++ b/resources/static/pages/signin.js
@@ -37,7 +37,8 @@ BrowserID.signIn = (function() {
         }, pageHelpers.getFailure(errors.authenticateWithAssertion, callback));
       }
       else {
-        $("#primary_no_login").fadeIn();
+        $("#primary_no_login").fadeIn(250);
+        setTimeout(complete.curry(callback), 250);
       }
     }, pageHelpers.getFailure(errors.provisioningPrimary, callback));
   }
diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js
index d0d7420eb0c4918ad0a566bc24a09dcb5c42702e..268cd7f5afe2ba3758f7041b28f1770986e31831 100644
--- a/resources/static/shared/user.js
+++ b/resources/static/shared/user.js
@@ -13,7 +13,8 @@ BrowserID.User = (function() {
       storage = bid.Storage,
       User, pollTimeout,
       provisioning = bid.Provisioning,
-      addressCache = {};
+      addressCache = {},
+      primaryAuthCache = {};
 
   function prepareDeps() {
     if (!jwk) {
@@ -205,7 +206,12 @@ BrowserID.User = (function() {
 
     reset: function() {
       provisioning = BrowserID.Provisioning;
+      User.resetCaches();
+    },
+
+    resetCaches: function() {
       addressCache = {};
+      primaryAuthCache = {};
     },
 
     /**
@@ -389,6 +395,31 @@ BrowserID.User = (function() {
      * @param {function} [onFailure] - called on failure
      */
     primaryUserAuthenticationInfo: function(email, info, onComplete, onFailure) {
+      var idInfo = storage.getEmail(email),
+          self=this;
+
+      primaryAuthCache = primaryAuthCache || {};
+
+      function complete(info) {
+        primaryAuthCache[email] = info;
+        onComplete && _.defer(onComplete.curry(info));
+      }
+
+      if(primaryAuthCache[email]) {
+        // If we have the info in our cache, we most definitely do not have to
+        // ask for it.
+        complete(primaryAuthCache[email]);
+        return;
+      }
+      else if(idInfo && idInfo.cert) {
+        // If we already have the info in storage, we know the user has a valid
+        // cert with their IdP, we say they are authenticated and pass back the
+        // appropriate info.
+        var userInfo = _.extend({authenticated: true}, idInfo, info);
+        complete(userInfo);
+        return;
+      }
+
       provisioning(
         { email: email, url: info.prov },
         function(keypair, cert) {
@@ -398,14 +429,14 @@ BrowserID.User = (function() {
             authenticated: true
           }, info);
 
-          onComplete(userInfo);
+          complete(userInfo);
         },
         function(error) {
           if (error.code === "primaryError" && error.msg === "user is not authenticated as target user") {
             var userInfo = _.extend({
               authenticated: false
             }, info);
-            onComplete(userInfo);
+            complete(userInfo);
           }
           else {
             onFailure(info);
diff --git a/resources/static/test/qunit/mocks/winchan.js b/resources/static/test/qunit/mocks/winchan.js
index 7ba3c211520e69c1f10def2579cde4213d749b57..43ecf8dc51c519fdf173298db27290ec23ac2d78 100644
--- a/resources/static/test/qunit/mocks/winchan.js
+++ b/resources/static/test/qunit/mocks/winchan.js
@@ -12,6 +12,7 @@ BrowserID.Mocks.WinChan = (function() {
     open: function(params, callback) {
       this.params = params;
       this.oncomplete = callback;
+      callback && callback(null, "yar");
     },
 
     onOpen: function() {
diff --git a/resources/static/test/qunit/pages/signin_unit_test.js b/resources/static/test/qunit/pages/signin_unit_test.js
index 560c95e889b475593a932bff3eace8e280607bd2..d03a0855648e59e9c7b9edc430155a612ea10f6d 100644
--- a/resources/static/test/qunit/pages/signin_unit_test.js
+++ b/resources/static/test/qunit/pages/signin_unit_test.js
@@ -179,6 +179,9 @@
     controller.emailSubmit(function() {
       controller.authWithPrimary(function() {
         provisioning.setStatus(provisioning.AUTHENTICATED);
+        // Before primaryAuthComplete is called, we reset the user caches to
+        // force re-fetching of what could have been stale user data.
+        user.resetCaches();
 
         controller.primaryAuthComplete(null, "yar", function() {
           network.checkAuth(function(status) {
diff --git a/resources/static/test/qunit/pages/signup_unit_test.js b/resources/static/test/qunit/pages/signup_unit_test.js
index 37422f5f660a1e992681e9239cbbbec11e6c4fac..6fe113b2b6ad8e6c5f08a01da9a3d7bc1733a6f7 100644
--- a/resources/static/test/qunit/pages/signup_unit_test.js
+++ b/resources/static/test/qunit/pages/signup_unit_test.js
@@ -7,6 +7,7 @@
   "use strict";
 
   var bid = BrowserID,
+      user = bid.User,
       network = bid.Network,
       xhr = bid.Mocks.xhr,
       WinChanMock = bid.Mocks.WinChan,
@@ -174,6 +175,10 @@
       bid.signUp.authWithPrimary(function() {
         // In real life the user would now be authenticated.
         provisioning.setStatus(provisioning.AUTHENTICATED);
+
+        // Before primaryAuthComplete is called, we reset the user caches to
+        // force re-fetching of what could have been stale user data.
+        user.resetCaches();
         bid.signUp.primaryAuthComplete(null, "success", function(status) {
           equal(status, true, "correct status");
           equal($("#congrats:visible").length, 1, "success notification is visible");
diff --git a/resources/static/test/qunit/resources/state_machine_unit_test.js b/resources/static/test/qunit/resources/state_machine_unit_test.js
index 8ee06c79ffd9dd616dcdf995d2317bf2fa7b1725..19609c205465fc9e2ec16f06c47db5e378558923 100644
--- a/resources/static/test/qunit/resources/state_machine_unit_test.js
+++ b/resources/static/test/qunit/resources/state_machine_unit_test.js
@@ -81,7 +81,13 @@
     ok(actions.called.doEmailConfirmed, "user was confirmed");
   });
 
-  test("primary_user calls doProvisionPrimaryUser", function() {
+  test("primary_user with already provisioned primary user calls doEmailChosen", function() {
+    storage.addEmail("testuser@testuser.com", { type: "primary", cert: "cert" });
+    mediator.publish("primary_user", { email: "testuser@testuser.com" });
+    ok(actions.called.doEmailChosen, "doEmailChosen called");
+  });
+
+  test("primary_user with unprovisioned primary user doProvisionPrimaryUser", function() {
     mediator.publish("primary_user", { email: "testuser@testuser.com" });
     ok(actions.called.doProvisionPrimaryUser, "doPrimaryUserProvisioned called");
   });