diff --git a/lib/db/json.js b/lib/db/json.js
index e49615c1fb4f5c2f495c9246cb8bcb4dbb695684..376a2a791cae9a968949a202772df5c2193d552f 100644
--- a/lib/db/json.js
+++ b/lib/db/json.js
@@ -236,7 +236,7 @@ exports.emailForVerificationSecret = function(secret, cb) {
   process.nextTick(function() {
     sync();
     if (!db.staged[secret]) return cb("no such secret");
-    cb(null, db.staged[secret].email, db.staged[secret].existing_user);
+    cb(null, db.staged[secret].email, db.staged[secret].existing_user, db.staged[secret].passwd);
   });
 };
 
diff --git a/lib/db/mysql.js b/lib/db/mysql.js
index b2e123ae6667e7c150963f627f0af30ba9c491bc..8c2d805e63fed01488e4b6f82b561198c98369c1 100644
--- a/lib/db/mysql.js
+++ b/lib/db/mysql.js
@@ -265,14 +265,14 @@ exports.haveVerificationSecret = function(secret, cb) {
 
 exports.emailForVerificationSecret = function(secret, cb) {
   client.query(
-    "SELECT email, existing_user FROM staged WHERE secret = ?", [ secret ],
+    "SELECT email, existing_user, passwd FROM staged WHERE secret = ?", [ secret ],
     function(err, rows) {
       if (err) return cb("database unavailable");
 
       // if the record was not found, fail out
       if (!rows || rows.length != 1) return cb("no such secret");
 
-      cb(null, rows[0].email, rows[0].existing_user);
+      cb(null, rows[0].email, rows[0].existing_user, rows[0].passwd);
     });
 };
 
@@ -291,7 +291,7 @@ exports.authForVerificationSecret = function(secret, cb) {
       if (o.passwd) return cb(null, o.passwd, o.existing_user);
 
       // otherwise, let's get the passwd from the user record
-      if (!o.existing_user) cb("no password for user");
+      if (!o.existing_user) return cb("no password for user");
 
       exports.checkAuth(o.existing_user, function(err, hash) {
         cb(err, hash, o.existing_user);
@@ -337,8 +337,9 @@ exports.gotVerificationSecret = function(secret, cb) {
       if (err) {
         logUnexpectedError(err);
         cb(err);
-      } else if (rows.length === 0) cb("unknown secret");
-      else {
+      } else if (rows.length === 0) {
+        cb("unknown secret");
+      } else {
         var o = rows[0];
 
         // delete the record
diff --git a/lib/wsapi/complete_email_addition.js b/lib/wsapi/complete_email_addition.js
index fcff7281387b49d78fb53a05ad4b74187dd36db2..7756fcf5b9cc8bb06cc14c7ba7768901854c6cad 100644
--- a/lib/wsapi/complete_email_addition.js
+++ b/lib/wsapi/complete_email_addition.js
@@ -21,13 +21,40 @@ exports.process = function(req, res) {
   //
   // 1. you must already be authenticated as the user who initiated the verification
   // 2. you must provide the password of the initiator.
-  //
+
+  // TRANSITIONAL CODE COMMENT
+  // for issue 1000 we moved initial password selection to the browserid dialog (from
+  // the verification page).  Rolling out this change causes some temporal pain.
+  // Outstannding verification links sent before the change was deployed will have
+  // email addition requests that require passwords without passwords in the stage table.
+  // When the verification page is loaded for
+  // these links, we prompt the user for a password.  That password is sent up with
+  // the request.  this code and comment should all be purged after the new code
+  // has been in production for 2 weeks.
+
+  var transitionalPassword = null;
+
+  // END TRANSITIONAL CODE COMMENT
+
+
   db.authForVerificationSecret(req.body.token, function(err, initiator_hash, initiator_uid) {
     if (err) {
       logger.info("unknown verification secret: " + err);
       return wsapi.databaseDown(res, err);
     }
 
+    // TRANSITIONAL CODE
+    if (!initiator_hash) {
+      if (!req.body.pass) return httputils.authRequired(res, "password required");
+      var err = wsapi.checkPassword(req.body.pass);
+      if (err) {
+        logger.warn("invalid password received: " + err);
+        return httputils.badRequest(res, err);
+      }
+      transitionalPassword = req.body.pass;
+      postAuthentication();
+    } else
+    // END TRANSITIONAL CODE
     if (req.session.userid === initiator_uid) {
       postAuthentication();
     } else if (typeof req.body.pass === 'string') {
@@ -53,6 +80,23 @@ exports.process = function(req, res) {
         } else {
           wsapi.authenticateSession(req.session, uid, 'password');
           res.json({ success: true });
+
+          // TRANSITIONAL CODE
+          if (transitionalPassword) {
+            wsapi.bcryptPassword(transitionalPassword, function(err, hash) {
+              if (err) {
+                logger.warn("couldn't bcrypt pass for old verification link: " + err);
+                return;
+              }
+
+              db.updatePassword(uid, hash, function(err) {
+                if (err) {
+                  logger.warn("couldn't bcrypt pass for old verification link: " + err);
+                }
+              });
+            });
+          }
+          // END TRANSITIONAL CODE
         }
       });
     };
diff --git a/lib/wsapi/complete_user_creation.js b/lib/wsapi/complete_user_creation.js
index e507e0f95e1396268a4f180100f00191ee469656..5b87c3ec9af756562a37076fb88a2e9f09713f99 100644
--- a/lib/wsapi/complete_user_creation.js
+++ b/lib/wsapi/complete_user_creation.js
@@ -28,18 +28,36 @@ exports.process = function(req, res) {
   // and then control a browserid account that they can use to prove they own
   // the email address of the attacked.
 
+  // TRANSITIONAL CODE COMMENT
+  // for issue 1000 we moved initial password selection to the browserid dialog (from
+  // the verification page).  Rolling out this change causes some temporal pain.
+  // Outstannding verification links sent before the change was deployed will have
+  // new user requests without passwords.  When the verification page is loaded for
+  // these links, we prompt the user for a password.  That password is sent up with
+  // the request.  this code and comment should all be purged after the new code
+  // has been in production for 2 weeks.
+  // END TRANSITIONAL CODE COMMENT
+
   // is this the same browser?
   if (typeof req.session.pendingCreation === 'string' &&
       req.body.token === req.session.pendingCreation) {
-    postAuthentication();
+    return postAuthentication();
   }
   // is a password provided?
   else if (typeof req.body.pass === 'string') {
     return db.authForVerificationSecret(req.body.token, function(err, hash) {
+      // TRANSITIONAL CODE
+      // if hash is null, no password was provided during verification and
+      // this is an old-style verification.  We accept the password and will
+      // update it after the verification is complete.
+      if (err == 'no password for user' || !hash) return postAuthentication();
+      // END TRANSITIONAL CODE
+
       if (err) {
         logger.warn("couldn't get password for verification secret: " + err);
         return wsapi.databaseDown(res, err);
       }
+
       bcrypt.compare(req.body.pass, hash, function (err, success) {
         if (err) {
           logger.warn("max load hit, failing on auth request with 503: " + err);
@@ -47,7 +65,7 @@ exports.process = function(req, res) {
         } else if (!success) {
           return httputils.authRequired(res, "password mismatch");
         } else {
-          postAuthentication();
+          return postAuthentication();
         }
       });
     });
@@ -65,19 +83,58 @@ exports.process = function(req, res) {
 
       if (!known) return res.json({ success: false} );
 
-      db.gotVerificationSecret(req.body.token, function(err, email, uid) {
-        if (err) {
-          logger.warn("couldn't complete email verification: " + err);
-          wsapi.databaseDown(res, err);
-        } else {
-          // 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',
-                                    config.get('ephemeral_session_duration_ms'));
-          res.json({ success: true });
+      // TRANSITIONAL CODE
+      // user is authorized (1 or 2 above) OR user has no password set, in which
+      // case for a short time we'll accept the password provided with the verification
+      // link, and set it as theirs.
+      var transitionalPassword = null;
+
+      db.authForVerificationSecret(req.body.token, function(err, hash) {
+        if (err == 'no password for user' || !hash) {
+          if (!req.body.pass) return httputils.authRequired(res, "password required");
+          err = wsapi.checkPassword(req.body.pass);
+          if (err) {
+            logger.warn("invalid password received: " + err);
+            return httputils.badRequest(res, err);
+          }
+          transitionalPassword = req.body.pass;
         }
+        completeCreation();
       });
+      // END TRANSITIONAL CODE
+
+      function completeCreation() {
+        db.gotVerificationSecret(req.body.token, function(err, email, uid) {
+          if (err) {
+            logger.warn("couldn't complete email verification: " + err);
+            wsapi.databaseDown(res, err);
+          } else {
+            // 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',
+                                      config.get('ephemeral_session_duration_ms'));
+            res.json({ success: true });
+
+            // TRANSITIONAL CODE
+            if (transitionalPassword) {
+              wsapi.bcryptPassword(transitionalPassword, function(err, hash) {
+                if (err) {
+                  logger.warn("couldn't bcrypt pass for old verification link: " + err);
+                  return;
+                }
+
+                db.updatePassword(uid, hash, function(err) {
+                  if (err) {
+                    logger.warn("couldn't bcrypt pass for old verification link: " + err);
+                  }
+                });
+              });
+            }
+            // END TRANSITIONAL CODE
+          }
+        });
+      }
     });
   }
 };
diff --git a/lib/wsapi/email_for_token.js b/lib/wsapi/email_for_token.js
index f492bcff595e0978c7f96e24f31b287fdceb8851..ac0a5d9543514ac4cb74c1ab3986eded0ad63421 100644
--- a/lib/wsapi/email_for_token.js
+++ b/lib/wsapi/email_for_token.js
@@ -19,7 +19,7 @@ exports.args = ['token'];
 exports.i18n = false;
 
 exports.process = function(req, res) {
-  db.emailForVerificationSecret(req.query.token, function(err, email, uid) {
+  db.emailForVerificationSecret(req.query.token, function(err, email, uid, hash) {
     if (err) {
       if (err === 'database unavailable') {
         httputils.serviceUnavailable(res, err);
@@ -30,24 +30,64 @@ exports.process = function(req, res) {
         });
       }
     } else {
-      // must the user authenticate?  This is true if they are not authenticated
-      // as the uid who initiated the verification, and they are not on the same
-      // browser as the initiator
-      var must_auth = true;
+      function checkMustAuth() {
+        // must the user authenticate?  This is true if they are not authenticated
+        // as the uid who initiated the verification, and they are not on the same
+        // browser as the initiator
+        var must_auth = true;
 
-      if (uid && req.session.userid === uid) {
-        must_auth = false;
+        if (uid && req.session.userid === uid) {
+          must_auth = false;
+        }
+        else if (!uid && typeof req.session.pendingCreation === 'string' &&
+                 req.query.token === req.session.pendingCreation) {
+          must_auth = false;
+        }
+
+        res.json({
+          success: true,
+          email: email,
+          must_auth: must_auth
+        });
+      }
+
+      // backwards compatibility - issue #1592
+      // if there is no password in the user record, and no password in the staged
+      // table, then we require a password be fetched from the user upon verification.
+      // these checks are temporary and should disappear in 1 trains time.
+      function needsPassword() {
+        // no password is set neither in the user table nor in the staged record.
+        // the user must pick a password
+        res.json({
+          success: true,
+          email: email,
+          needs_password: true
+        });
       }
-      else if (!uid && typeof req.session.pendingCreation === 'string' &&
-               req.query.token === req.session.pendingCreation) {
-        must_auth = false;
+
+      if (!hash) {
+        if (!uid) {
+          needsPassword();
+        } else {
+          db.checkAuth(uid, function(err, hash) {
+            if (err) {
+              return res.json({
+                success: false,
+                reason: err
+              });
+            }
+
+            if (!hash) {
+              needsPassword();
+            } else {
+              checkMustAuth();
+            }
+          });
+        }
+      } else {
+        checkMustAuth();
       }
 
-      res.json({
-        success: true,
-        email: email,
-        must_auth: must_auth
-      });
     }
   });
 };
diff --git a/resources/static/css/style.css b/resources/static/css/style.css
index 86b0b219f6f3b529e0ba677130ce4f0d0271ad76..802295daca104265ab0c2bf4feb716cb39357683 100644
--- a/resources/static/css/style.css
+++ b/resources/static/css/style.css
@@ -669,7 +669,7 @@ h1 {
   margin-bottom: 10px;
 }
 
-.siteinfo, #congrats, .password_entry, .enter_password .hint, #unknown_secondary, #primary_verify, .verify_primary .submit {
+.siteinfo, #congrats, .password_entry, #verify_password, .enter_password .hint, #unknown_secondary, #primary_verify, .verify_primary .submit {
   display: none;
 }
 
@@ -677,7 +677,7 @@ h1 {
   float: left;
 }
 
-.enter_password .password_entry, .known_secondary .password_entry,
+.enter_password .password_entry, .enter_verify_password #verify_password, .known_secondary .password_entry,
 .unknown_secondary #unknown_secondary, .verify_primary #verify_primary {
   display: block;
 }
diff --git a/resources/static/pages/verify_secondary_address.js b/resources/static/pages/verify_secondary_address.js
index 2ccfb0c804ec90c672114c6235fcbefee8b3e0df..607a5621c80d4b477de7ea572c4008b007d6a30a 100644
--- a/resources/static/pages/verify_secondary_address.js
+++ b/resources/static/pages/verify_secondary_address.js
@@ -17,6 +17,7 @@ BrowserID.verifySecondaryAddress = (function() {
       validation = bid.Validation,
       token,
       sc,
+      needsPassword,
       mustAuth,
       verifyFunction;
 
@@ -36,7 +37,11 @@ BrowserID.verifySecondaryAddress = (function() {
 
   function submit(oncomplete) {
     var pass = dom.getInner("#password") || undefined,
-        valid = !mustAuth || validation.password(pass);
+        vpass = dom.getInner("#vpassword") || undefined,
+        valid = (!needsPassword ||
+                    validation.passwordAndValidationPassword(pass, vpass))
+             && (!mustAuth ||
+                    validation.password(pass));
 
     if (valid) {
       user[verifyFunction](token, pass, function(info) {
@@ -56,13 +61,25 @@ BrowserID.verifySecondaryAddress = (function() {
       if(info) {
         showRegistrationInfo(info);
 
+        needsPassword = info.needs_password;
         mustAuth = info.must_auth;
 
-        if (mustAuth) {
+        if (needsPassword) {
+          // This is a fix for legacy users who started the user creation
+          // process without setting their password in the dialog.  If the user
+          // needs a password, they must set it now.  Once all legacy users are
+          // verified or their links invalidated, this flow can be removed.
+          dom.addClass("body", "enter_password");
+          dom.addClass("body", "enter_verify_password");
+          complete(oncomplete, true);
+        }
+        else if (mustAuth) {
+          // These are users who have set their passwords inside of the dialog.
           dom.addClass("body", "enter_password");
           complete(oncomplete, true);
         }
         else {
+          // These are users who do not have to set their passwords at all.
           submit(oncomplete);
         }
       }
diff --git a/resources/static/test/cases/pages/verify_secondary_address.js b/resources/static/test/cases/pages/verify_secondary_address.js
index a770237db60734b31e39a6d9f82553bb917b9bc5..d7eacaa574bb8d489c7a5897d0913c2060d43a6b 100644
--- a/resources/static/test/cases/pages/verify_secondary_address.js
+++ b/resources/static/test/cases/pages/verify_secondary_address.js
@@ -116,6 +116,7 @@
     xhr.useResult("mustAuth");
     createController(config, function() {
       xhr.useResult("valid");
+      testHasClass("body", "enter_password");
       controller.submit(function(status) {
         equal(status, true, "correct status");
         testHasClass("body", "complete");
@@ -134,4 +135,88 @@
     });
   });
 
+  asyncTest("must set password, successful login", function() {
+    xhr.useResult("needsPassword");
+    createController(config, function() {
+      xhr.useResult("valid");
+
+      $("#password").val("password");
+      $("#vpassword").val("password");
+
+      testHasClass("body", "enter_password");
+      testHasClass("body", "enter_verify_password");
+
+      controller.submit(function(status) {
+        equal(status, true, "correct status");
+        testHasClass("body", "complete");
+        start();
+      });
+    });
+  });
+
+  asyncTest("must set password, too short a password", function() {
+    xhr.useResult("needsPassword");
+    createController(config, function() {
+      xhr.useResult("valid");
+
+      $("#password").val("pass");
+      $("#vpassword").val("pass");
+
+      controller.submit(function(status) {
+        equal(status, false, "correct status");
+        testHelpers.testTooltipVisible();
+        start();
+      });
+    });
+  });
+
+  asyncTest("must set password, too long a password", function() {
+    xhr.useResult("needsPassword");
+    createController(config, function() {
+      xhr.useResult("valid");
+
+      var pass = testHelpers.generateString(81);
+      $("#password").val(pass);
+      $("#vpassword").val(pass);
+
+      controller.submit(function(status) {
+        equal(status, false, "correct status");
+        testHelpers.testTooltipVisible();
+        start();
+      });
+    });
+  });
+
+  asyncTest("must set password, missing verification password", function() {
+    xhr.useResult("needsPassword");
+    createController(config, function() {
+      xhr.useResult("valid");
+
+      $("#password").val("password");
+      $("#vpassword").val("");
+
+      controller.submit(function(status) {
+        equal(status, false, "correct status");
+        testHelpers.testTooltipVisible();
+        start();
+      });
+    });
+  });
+
+  asyncTest("must set password, mismatched passwords", function() {
+    xhr.useResult("needsPassword");
+    createController(config, function() {
+      xhr.useResult("valid");
+
+      $("#password").val("password");
+      $("#vpassword").val("password1");
+
+      controller.submit(function(status) {
+        equal(status, false, "correct status");
+        testHelpers.testTooltipVisible();
+        start();
+      });
+    });
+  });
+
 }());
diff --git a/resources/static/test/mocks/xhr.js b/resources/static/test/mocks/xhr.js
index 8b505f997d4680903d922e8de6e2a90d78f08cc1..12c6122513905fee03db42473b6173355cc1869e 100644
--- a/resources/static/test/mocks/xhr.js
+++ b/resources/static/test/mocks/xhr.js
@@ -34,6 +34,7 @@ BrowserID.Mocks.xhr = (function() {
       "get /wsapi/session_context contextAjaxError": undefined,
       "get /wsapi/email_for_token?token=token valid": { email: "testuser@testuser.com" },
       "get /wsapi/email_for_token?token=token mustAuth": { email: "testuser@testuser.com", must_auth: true },
+      "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, userid: 1 },
       "post /wsapi/authenticate_user invalid": { success: false },
diff --git a/resources/views/add_email_address.ejs b/resources/views/add_email_address.ejs
index fa6fbd891b20f14c23532b9497da17adf3127e16..45b711cec7d8d7b53a9da83b7f2c1f933de87ca4 100644
--- a/resources/views/add_email_address.ejs
+++ b/resources/views/add_email_address.ejs
@@ -31,6 +31,20 @@
                       <%= gettext('Password must be between 8 and 80 characters long.') %>
                     </div>
                 </li>
+
+                <li class="password_entry" id="verify_password">
+                    <label class="serif" for="vpassword"><%= gettext('Verify Password') %></label>
+                    <input class="sans" id="vpassword" placeholder="<%= gettext('Repeat Password') %>" type="password" maxlength="80">
+
+                    <div id="vpassword_required" class="tooltip" for="vpassword">
+                      <%= gettext('Verification password is required.') %>
+                    </div>
+
+                    <div class="tooltip" id="passwords_no_match" for="vpassword">
+                      <%= gettext ('Passwords do not match.') %>
+                    </div>
+
+                </li>
             </ul>
 
             <div class="submit cf password_entry">
diff --git a/resources/views/verify_email_address.ejs b/resources/views/verify_email_address.ejs
index a73c80ead95230830d13bc8f218e5e84e878c314..1f275d3fe8c3362f98b6360c58484d401e2a9e9c 100644
--- a/resources/views/verify_email_address.ejs
+++ b/resources/views/verify_email_address.ejs
@@ -18,6 +18,7 @@
                     <label class="serif" for="email"><%= gettext('Email Address') %></label>
                     <input class="youraddress sans" id="email" placeholder="<%= gettext('Your Email') %>" type="email" value="" disabled="disabled" maxlength="254" />
                 </li>
+
                 <li class="password_entry">
                     <label class="serif" for="password"><%= gettext('Password') %></label>
                     <input class="sans" id="password" placeholder="<%= gettext('Your Password') %>" type="password" autofocus maxlength=80 />
@@ -30,6 +31,20 @@
                       <%= gettext('Password must be between 8 and 80 characters long.') %>
                     </div>
                 </li>
+
+                <li class="password_entry" id="verify_password">
+                    <label class="serif" for="vpassword"><%= gettext('Verify Password') %></label>
+                    <input class="sans" id="vpassword" placeholder="<%= gettext('Repeat Password') %>" type="password" maxlength="80">
+
+                    <div id="vpassword_required" class="tooltip" for="vpassword">
+                      <%= gettext('Verification password is required.') %>
+                    </div>
+
+                    <div class="tooltip" id="passwords_no_match" for="vpassword">
+                      <%= gettext ('Passwords do not match.') %>
+                    </div>
+
+                </li>
             </ul>
 
             <div class="submit cf password_entry">