diff --git a/example/rp/index.html b/example/rp/index.html
index 5f0b24bdcae337ead4254e40073d00b9b1c69995..19ddc0533b343d33ef00de82318cc0f11ddf8655 100644
--- a/example/rp/index.html
+++ b/example/rp/index.html
@@ -73,6 +73,10 @@ pre {
       <input type="checkbox" id="siteLogo">
       <label for="siteLogo">Supply Site Logo</label><br />
     </li>
+    </li><li>
+      <input type="checkbox" id="returnTo">
+      <label for="returnTo">Supply returnTo</label><br />
+    </li>
     </li><li>
       <input type="text" id="requiredEmail" width="80">
       <label for="requiredEmail">Require a specific email</label><br />
@@ -193,6 +197,7 @@ $(document).ready(function() {
       termsOfService: $('#termsOfService').attr('checked') ? "/TOS.html" : undefined,
       siteName: $('#siteName').attr('checked') ? "Persona Test Relying Party" : undefined,
       siteLogo: $('#siteLogo').attr('checked') ? "/i/logo.png" : undefined,
+      returnTo: $('#returnTo').attr('checked') ? "/postVerificationReturn.html" : undefined,
       requiredEmail: requiredEmail,
       oncancel: function() {
         loggit("oncancel");
diff --git a/example/rp/postVerificationReturn.html b/example/rp/postVerificationReturn.html
new file mode 100644
index 0000000000000000000000000000000000000000..518c7abbcd7e3073215336abff5adfb523c97815
--- /dev/null
+++ b/example/rp/postVerificationReturn.html
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<!-- 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/. -->
+
+<html>
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="initial-scale=1.0; maximum-scale=1.0; width=device-width;">
+<title>
+Persona Relying Party Post Verification Return
+</title>
+<style type="text/css">
+
+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 auto 1.5em auto; }
+.intro { font-size: 1.2em;  }
+.specify, .session { 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;
+  -moz-border-radius: 4px;
+  -webkit-border-radius: 4px;
+  border-radius: 4px;
+  padding: .5em;
+  margin: .5em;
+  color: #ccc;
+  background-color: #333;
+/*  white-space: pre;*/
+  font-size: .9em;
+  word-wrap: break-word;
+}
+
+.specify ul { padding-left: 0px; }
+.specify li { list-style: none; }
+
+@media screen and (max-width: 640px) {
+  .intro, .output, .step {
+    width: 90%;
+  }
+}
+
+</style>
+</head>
+<body>
+<div class="title">
+  Persona Relying Party Post Verification Return
+</div>
+
+<div class="intro">
+  This is part of a RP for testing, it is the returnTo for post-verification redirect.
+  <p>
+    <a href="/">Return to RP Test Page</a>
+  </p>
+</div>
+
+<div class="loginEvents">
+  <h2>logins</h2>
+  <pre> ... </pre>
+</div>
+
+
+<div class="readiness">
+  <h2>readiness</h2>
+  <pre> ... </pre>
+</div>
+
+</body>
+
+<script src="jquery-min.js"></script>
+<script src="https://browserid.org/include.js"></script>
+<script>
+
+try {
+  var storage = localStorage;
+}
+catch(e) {
+  // Fx with cookies disabled with blow up when trying to access localStorage.
+  storage = {};
+}
+
+
+function loggit() {
+  try {
+    console.log.apply(console, arguments);
+  } catch(e) {}
+}
+
+var serial = 1;
+
+// a function to check an assertion against the server
+function checkAssertion(assertion) {
+  $.ajax({
+    url: "/process_assertion",
+    type: "post",
+    dataType: "json",
+    data: {
+      assertion: assertion,
+      audience: window.location.protocol + "//" + window.location.host
+    },
+    success: function(data, textStatus, jqXHR) {
+      var old = $(".loginEvents > pre").text() + "\n";
+      $(".loginEvents > pre").text(old + JSON.stringify(data, null, 4));
+    },
+    error: function(jqXHR, textStatus, errorThrown) {
+      var resp = jqXHR.responseText ? JSON.parse(jqXHR.responseText) : errorThrown;
+      $(".loginEvents > pre").text(resp);
+    }
+  });
+};
+
+navigator.id.watch({
+  loggedInEmail: (storage.loggedInUser === 'null') ? null : storage.loggedInUser,
+  onready: function () {
+    loggit("onready");
+    var txt = serial++ + ' navigator.id ready at ' + (new Date).toString();
+    $(".readiness > pre").text(txt);
+
+  },
+  onlogin: function (assertion) {
+    loggit("onlogin");
+    var txt = serial++ + ' got assertion at ' + (new Date).toString();
+    $(".loginEvents > pre").text(txt);
+
+    checkAssertion(assertion);
+
+    $(".specify button.assertion").removeAttr('disabled');
+  },
+  onlogout: function () {
+    loggit("onlogout");
+    var txt = serial++ + ' logout callback invoked at ' + (new Date).toString();
+    $(".logoutEvents > pre").text(txt);
+  }
+});
+
+</script>
+
+</html>
diff --git a/lib/static_resources.js b/lib/static_resources.js
index d86bfd740f699c1873d26f50b092a0b80ca5fac3..3e0cf15d6f00a382af7dcad03a94d47d8fcbdf09 100644
--- a/lib/static_resources.js
+++ b/lib/static_resources.js
@@ -22,6 +22,7 @@ var common_js = [
   '/lib/bidbundle.js',
   '/lib/ejs.js',
   '/lib/micrajax.js',
+  '/lib/urlparse.js',
   '/shared/javascript-extensions.js',
   '/i18n/:locale/client.json',
   '/shared/gettext.js',
@@ -75,8 +76,6 @@ var dialog_min_js = '/production/:locale/dialog.js';
 var dialog_js = und.flatten([
   common_js,
   [
-    '/lib/urlparse.js',
-
     '/shared/command.js',
     '/shared/history.js',
     '/shared/state_machine.js',
diff --git a/resources/static/css/style.css b/resources/static/css/style.css
index 4cecf7645473b802c065ad18a1a9f52e81cf91b8..35bfa524ab66efd353488ddd596061022bb3fc77 100644
--- a/resources/static/css/style.css
+++ b/resources/static/css/style.css
@@ -557,12 +557,16 @@ button.delete:active {
 }
 
 #congrats .siteinfo {
-    margin-top: 10px;
+  margin-top: 10px;
 }
 
 #congrats .website {
-    display: block;
-    text-align: center;
+  display: block;
+  text-align: center;
+}
+
+#redirection {
+  text-align: center;
 }
 
 
diff --git a/resources/static/dialog/controllers/dialog.js b/resources/static/dialog/controllers/dialog.js
index 8d9725d5120c29f38b095d7a1cb747e0706eeb5e..36e66dca60e3ccc6310246344cb029f22d637376 100644
--- a/resources/static/dialog/controllers/dialog.js
+++ b/resources/static/dialog/controllers/dialog.js
@@ -184,6 +184,14 @@ BrowserID.Modules.Dialog = (function() {
           params.siteName = _.escape(paramsFromRP.siteName);
         }
 
+        // returnTo is used for post verification redirection.  Redirect back
+        // to the path specified by the RP.
+        if (paramsFromRP.returnTo) {
+          var returnTo = fixupAbsolutePath(origin_url, paramsFromRP.returnTo);
+          user.setReturnTo(returnTo);
+        }
+
+
         if (hash.indexOf("#CREATE_EMAIL=") === 0) {
           var email = hash.replace(/#CREATE_EMAIL=/, "");
           if (!bid.verifyEmail(email))
diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js
index fafc25ff8844ad40a38f15922161f90d34222c9e..e3b60e5ff29ed5e25d3be57dd2cbeebc1b41328a 100644
--- a/resources/static/include_js/include.js
+++ b/resources/static/include_js/include.js
@@ -1069,6 +1069,9 @@
       // don't do duplicative work
       if (commChan) commChan.notify({ method: 'dialog_running' });
 
+      // returnTo is used for post-email-verification redirect
+      if (!options.returnTo) options.returnTo = document.location.pathname;
+
       w = WinChan.open({
         url: ipServer + '/sign_in',
         relay_url: ipServer + '/relay',
diff --git a/resources/static/pages/verify_secondary_address.js b/resources/static/pages/verify_secondary_address.js
index e94297adf8fce4786b99f8ebcbfd80ac0985efa6..df4561ec37b6707e3401afafd4f7c07225ba283a 100644
--- a/resources/static/pages/verify_secondary_address.js
+++ b/resources/static/pages/verify_secondary_address.js
@@ -9,6 +9,7 @@ BrowserID.verifySecondaryAddress = (function() {
   var ANIMATION_TIME=250,
       bid = BrowserID,
       user = bid.User,
+      storage = bid.Storage,
       errors = bid.Errors,
       pageHelpers = bid.PageHelpers,
       dom = bid.DOM,
@@ -20,7 +21,13 @@ BrowserID.verifySecondaryAddress = (function() {
       sc,
       needsPassword,
       mustAuth,
-      verifyFunction;
+      verifyFunction,
+      doc = document,
+      REDIRECT_SECONDS = 5,
+      secondsRemaining = REDIRECT_SECONDS,
+      email,
+      redirectTo,
+      redirectTimeout;  // set in config if available, use REDIRECT_SECONDS otw.
 
   function showError(el, oncomplete) {
     dom.hide(".hint,#signUpForm");
@@ -30,26 +37,56 @@ BrowserID.verifySecondaryAddress = (function() {
   function showRegistrationInfo(info) {
     dom.setInner("#email", info.email);
 
-    if (info.origin) {
-      dom.setInner(".website", info.origin);
+    if (info.returnTo) {
+      dom.setInner(".website", info.returnTo);
+      updateRedirectTimeout();
       dom.show(".siteinfo");
     }
   }
 
+  function updateRedirectTimeout() {
+    if (secondsRemaining > 0) {
+      dom.setInner("#redirectTimeout", secondsRemaining);
+
+      secondsRemaining--;
+      setTimeout(updateRedirectTimeout, 1000);
+    }
+  }
+
   function submit(oncomplete) {
     var pass = dom.getInner("#password") || undefined,
         vpass = dom.getInner("#vpassword") || undefined,
-        valid = (!needsPassword ||
+        inputValid = (!needsPassword ||
                     validation.passwordAndValidationPassword(pass, vpass))
              && (!mustAuth ||
                     validation.password(pass));
 
-    if (valid) {
+    if (inputValid) {
       user[verifyFunction](token, pass, function(info) {
         dom.addClass("body", "complete");
 
-        var selector = info.valid ? "#congrats" : "#cannotcomplete";
-        pageHelpers.replaceFormWithNotice(selector, complete.curry(oncomplete, info.valid));
+        var verified = info.valid,
+            selector = verified ? "#congrats" : "#cannotcomplete";
+
+        pageHelpers.replaceFormWithNotice(selector, function() {
+          if (redirectTo && verified) {
+
+            // set the loggedIn status for the site.  This allows us to get
+            // a silent assertion without relying on the dialog to set the
+            // loggedIn status for the domain.  This is useful when the user
+            // closes the dialog OR if redirection happens before the dialog
+            // has had a chance to finish its business.
+            storage.setLoggedIn(URLParse(redirectTo).originOnly(), email);
+
+            setTimeout(function() {
+              doc.location.href = redirectTo;
+              complete(oncomplete, verified);
+            }, redirectTimeout);
+          }
+          else {
+            complete(oncomplete, verified);
+          }
+        });
       }, function(info) {
         if (info.network && info.network.status === 401) {
           tooltip.showTooltip("#cannot_authenticate");
@@ -67,7 +104,9 @@ BrowserID.verifySecondaryAddress = (function() {
 
   function startVerification(oncomplete) {
     user.tokenInfo(token, function(info) {
-      if(info) {
+      if (info) {
+        redirectTo = info.returnTo;
+        email = info.email;
         showRegistrationInfo(info);
 
         needsPassword = info.needs_password;
@@ -105,6 +144,12 @@ BrowserID.verifySecondaryAddress = (function() {
 
       token = options.token;
       verifyFunction = options.verifyFunction;
+      doc = options.document || document;
+
+      redirectTimeout = options.redirectTimeout;
+      if (typeof redirectTimeout === "undefined") {
+        redirectTimeout = REDIRECT_SECONDS * 1000;
+      }
 
       startVerification(options.ready);
 
diff --git a/resources/static/shared/storage.js b/resources/static/shared/storage.js
index 1fa52161a9da6e0b65c7c0bad02e79dfe9682800..7b4e768ac2d2ab3d37e36365fed4b919fd3c039b 100644
--- a/resources/static/shared/storage.js
+++ b/resources/static/shared/storage.js
@@ -50,7 +50,6 @@ BrowserID.Storage = (function() {
   function clear() {
     storage.removeItem("emails");
     storage.removeItem("tempKeypair");
-    storage.removeItem("stagedOnBehalfOf");
     storage.removeItem("siteInfo");
     storage.removeItem("managePage");
   }
@@ -147,29 +146,50 @@ BrowserID.Storage = (function() {
     }
   }
 
-  function setStagedOnBehalfOf(origin) {
-    storage.stagedOnBehalfOf = JSON.stringify({
+  function setReturnTo(returnToURL) {
+    storage.returnTo = JSON.stringify({
       at: new Date().toString(),
-      origin: origin
+      url: returnToURL
     });
   }
 
-  function getStagedOnBehalfOf() {
-    var origin;
+  function getReturnTo() {
+    var returnToURL;
 
+    // XXX - The transitional code is to make sure any emails that were staged using
+    // the old setStagedOnBehalfOf still work with the new API.  This should be
+    // able to be removed by mid-July 2012.
     try {
-      var staged = JSON.parse(storage.stagedOnBehalfOf);
+      // BEGIN TRANSITIONAL CODE
+      if (storage.returnTo) {
+      // END TRANSITIONAL CODE
+        var staged = JSON.parse(storage.returnTo);
+
+        if (staged) {
+          if ((new Date() - new Date(staged.at)) > (5 * 60 * 1000)) throw "stale";
+          if (typeof(staged.url) !== 'string') throw "malformed";
+          returnToURL = staged.url;
+        }
+      // BEGIN TRANSITIONAL CODE
+      }
+      else if(storage.stagedOnBehalfOf) {
+        var staged = JSON.parse(storage.stagedOnBehalfOf);
 
-      if (staged) {
-        if ((new Date() - new Date(staged.at)) > (5 * 60 * 1000)) throw "stale";
-        if (typeof(staged.origin) !== 'string') throw "malformed";
-        origin = staged.origin;
+        if (staged) {
+          if ((new Date() - new Date(staged.at)) > (5 * 60 * 1000)) throw "stale";
+          if (typeof(staged.origin) !== 'string') throw "malformed";
+          returnToURL = staged.origin;
+        }
       }
+      // END TRANSITIONAL CODE
     } catch (x) {
+      storage.removeItem("returnTo");
+      // BEGIN TRANSITIONAL CODE
       storage.removeItem("stagedOnBehalfOf");
+      // END TRANSITIONAL CODE
     }
 
-    return origin;
+    return returnToURL;
   }
 
   function siteSet(site, key, value) {
@@ -570,7 +590,7 @@ BrowserID.Storage = (function() {
     clear: clear,
     storeTemporaryKeypair: storeTemporaryKeypair,
     retrieveTemporaryKeypair: retrieveTemporaryKeypair,
-    setStagedOnBehalfOf: setStagedOnBehalfOf,
-    getStagedOnBehalfOf: getStagedOnBehalfOf
+    setReturnTo: setReturnTo,
+    getReturnTo: getReturnTo
   };
 }());
diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js
index d966fe5d0f57c3a03421602d4df365f0ee417c85..ab493ae9f4870da8bc26472ff8a08f69b53b4095 100644
--- a/resources/static/shared/user.js
+++ b/resources/static/shared/user.js
@@ -113,7 +113,7 @@ BrowserID.User = (function() {
           // As soon as the registration comes back as complete, we should
           // ensure that the stagedOnBehalfOf is cleared so there is no stale
           // data.
-          storage.setStagedOnBehalfOf("");
+          storage.setReturnTo("");
 
           // To avoid too many address_info requests, returns from each
           // address_info request are cached.  If the user is doing
@@ -273,6 +273,14 @@ BrowserID.User = (function() {
       return origin.replace(/^.*:\/\//, "").replace(/:\d*$/, "");
     },
 
+    setReturnTo: function(returnTo) {
+      this.returnTo = returnTo;
+    },
+
+    getReturnTo: function() {
+      return this.returnTo;
+    },
+
     /**
      * Create a user account - this creates an user account that must be verified.
      * @method createSecondaryUser
@@ -282,15 +290,13 @@ BrowserID.User = (function() {
      * @param {function} [onFailure] - Called on error.
      */
     createSecondaryUser: function(email, password, onComplete, onFailure) {
-      // Used on the main site when the user verifies - we try to show them
-      // what URL they came from.
-
-      // XXX - this will have to be updated to either store both the hostname
-      // and the exact URL of the RP or just the URL of the RP and the origin
-      // is extracted from that.
-      storage.setStagedOnBehalfOf(User.getHostname());
-
-      network.createUser(email, password, origin, onComplete, onFailure);
+      network.createUser(email, password, origin, function(created) {
+        // Used on the main site when the user verifies - once verification
+        // is complete, the user is redirected back to the RP and logged in.
+        var site = User.getReturnTo();
+        if (created && site) storage.setReturnTo(site);
+        complete(onComplete, created);
+      }, onFailure);
     },
 
     /**
@@ -481,10 +487,10 @@ BrowserID.User = (function() {
     tokenInfo: function(token, onComplete, onFailure) {
       network.emailForVerificationToken(token, function (info) {
         if(info) {
-          info = _.extend(info, { origin: storage.getStagedOnBehalfOf() });
+          info = _.extend(info, { returnTo: storage.getReturnTo() });
         }
 
-        onComplete && onComplete(info);
+        complete(onComplete, info);
       }, onFailure);
 
     },
@@ -507,8 +513,8 @@ BrowserID.User = (function() {
             var result = invalidInfo;
 
             if(valid) {
-              result = _.extend({ valid: valid, origin: storage.getStagedOnBehalfOf() }, info);
-              storage.setStagedOnBehalfOf("");
+              result = _.extend({ valid: valid, returnTo: storage.getReturnTo() }, info);
+              storage.setReturnTo("");
             }
 
             complete(onComplete, result);
@@ -571,13 +577,16 @@ BrowserID.User = (function() {
       User.isEmailRegistered(email, function(registered) {
         if (registered) {
           network.requestPasswordReset(email, password, origin, function(reset) {
-            var status = {
-              success: reset
-            };
+            var status = { success: reset };
 
             if (!reset) status.reason = "throttle";
+            // Used on the main site when the user verifies - once
+            // verification is complete, the user is redirected back to the
+            // RP and logged in.
+            var site = User.getReturnTo();
+            if (reset && site) storage.setReturnTo(site);
 
-            if (onComplete) onComplete(status);
+            complete(onComplete, status);
           }, onFailure);
         }
         else if (onComplete) {
@@ -806,10 +815,13 @@ BrowserID.User = (function() {
      */
     addEmail: function(email, password, onComplete, onFailure) {
       network.addSecondaryEmail(email, password, origin, function(added) {
-        if (added) storage.setStagedOnBehalfOf(User.getHostname());
+        // Used on the main site when the user verifies - once verification
+        // is complete, the user is redirected back to the RP and logged in.
+        var returnTo = User.getReturnTo();
+        if (added && returnTo) storage.setReturnTo(returnTo);
 
         // we no longer send the keypair, since we will certify it later.
-        if (onComplete) onComplete(added);
+        complete(onComplete, added);
       }, onFailure);
     },
 
@@ -871,8 +883,8 @@ BrowserID.User = (function() {
             var result = invalidInfo;
 
             if(valid) {
-              result = _.extend({ valid: valid, origin: storage.getStagedOnBehalfOf() }, info);
-              storage.setStagedOnBehalfOf("");
+              result = _.extend({ valid: valid, returnTo: storage.getReturnTo() }, info);
+              storage.setReturnTo("");
             }
 
             complete(onComplete, result);
diff --git a/resources/static/test/cases/controllers/dialog.js b/resources/static/test/cases/controllers/dialog.js
index ed8aba8d28f0a540b76b9d7790ee3d1af438e1e1..aedd54d97475b720ed0affd26f640161948dfd04 100644
--- a/resources/static/test/cases/controllers/dialog.js
+++ b/resources/static/test/cases/controllers/dialog.js
@@ -15,6 +15,7 @@
       testErrorNotVisible = testHelpers.testErrorNotVisible,
       screens = bid.Screens,
       xhr = bid.Mocks.xhr,
+      user = bid.User,
       HTTP_TEST_DOMAIN = "http://testdomain.org",
       HTTPS_TEST_DOMAIN = "https://testdomain.org",
       TESTEMAIL = "testuser@testuser.com",
@@ -572,8 +573,6 @@
           siteLogo: siteLogo
         });
 
-        start();
-
         testHelpers.testObjectValuesEqual(startInfo, {
           siteLogo: encodeURI(HTTP_TEST_DOMAIN + siteLogo)
         });
@@ -582,10 +581,42 @@
         start();
       }
     });
-
   });
 
 
+  asyncTest("get with returnTo with https - not allowed", function() {
+    createController({
+      ready: function() {
+        var URL = HTTP_TEST_DOMAIN + "/path";
+
+        mediator.subscribe("start", function(msg, info) {
+          ok(false, "unexpected start");
+        });
+
+        var retval = controller.get(HTTP_TEST_DOMAIN, {
+          returnTo: URL
+        });
 
+        equal(retval, "must be an absolute path: (" + URL + ")", "expected error");
+        testErrorVisible();
+        start();
+      }
+    });
+  });
+
+  asyncTest("get with absolute path returnTo - allowed", function() {
+    createController({
+      ready: function() {
+        mediator.subscribe("start", function(msg, info) {
+          equal(user.getReturnTo(), HTTPS_TEST_DOMAIN + "/path", "returnTo correctly set");
+          start();
+        });
+
+        var retval = controller.get(HTTPS_TEST_DOMAIN, {
+          returnTo: "/path"
+        });
+      }
+    });
+  });
 }());
 
diff --git a/resources/static/test/cases/pages/verify_secondary_address.js b/resources/static/test/cases/pages/verify_secondary_address.js
index 630667b19f554b2c634d860f3fb2fbc20ec1f687..0d74afe90af7dc39c853bc3f8860a11adfbac517 100644
--- a/resources/static/test/cases/pages/verify_secondary_address.js
+++ b/resources/static/test/cases/pages/verify_secondary_address.js
@@ -9,6 +9,7 @@
   var bid = BrowserID,
       storage = bid.Storage,
       xhr = bid.Mocks.xhr,
+      WindowMock = bid.Mocks.WindowMock,
       dom = bid.DOM,
       testHelpers = bid.TestHelpers,
       testHasClass = testHelpers.testHasClass,
@@ -17,7 +18,8 @@
       config = {
         token: "token",
         verifyFunction: "verifyEmail"
-      };
+      },
+      doc;
 
   module("pages/verify_secondary_address", {
     setup: function() {
@@ -33,6 +35,8 @@
   function createController(options, callback) {
     controller = BrowserID.verifySecondaryAddress.create();
     options = options || {};
+    options.document = doc = new WindowMock().document;
+    options.redirectTimeout = 0;
     options.ready = callback;
     controller.start(options);
   }
@@ -67,13 +71,16 @@
   });
 
   asyncTest("no password: start with good token and site", function() {
-    storage.setStagedOnBehalfOf("persona.org");
+    var returnTo = "https://test.domain/path";
+    storage.setReturnTo(returnTo);
 
     createController(config, function() {
       testEmail();
       ok($(".siteinfo").is(":visible"), "siteinfo is visible when we say what it is");
-      equal($(".website:nth(0)").text(), "persona.org", "origin is updated");
+      equal($(".website:nth(0)").text(), returnTo, "website is updated");
       testHasClass("body", "complete");
+      equal(doc.location.href, returnTo, "redirection occurred to correct URL");
+      equal(storage.getLoggedIn("https://test.domain"), "testuser@testuser.com", "logged in status set");
       start();
     });
   });
diff --git a/resources/static/test/cases/shared/storage.js b/resources/static/test/cases/shared/storage.js
index c9406c84a21ef077b4a150f956a3666a0eccc505..5a7048c00799dfa54ebd351407cf45199264aec9 100644
--- a/resources/static/test/cases/shared/storage.js
+++ b/resources/static/test/cases/shared/storage.js
@@ -164,12 +164,9 @@
     // XXX needs a test
   });
 
-  test("setStagedOnBehalfOf", function() {
-    // XXX needs a test
-  });
-
-  test("getStagedOnBehalfOf", function() {
-    // XXX needs a test
+  test("setReturnTo", function() {
+    storage.setReturnTo("http://some.domain/path");
+    equal(storage.getReturnTo(), "http://some.domain/path", "setReturnTo/getReturnTo working as expected");
   });
 
   test("signInEmail.set/.get/.remove - set, get, and remove the signInEmail", function() {
diff --git a/resources/static/test/cases/shared/user.js b/resources/static/test/cases/shared/user.js
index 155883d5e53e4b320d3a3fcfc67a899e084b5248..a252cd357f1f73bc34d6c3bfed4755514813f2db 100644
--- a/resources/static/test/cases/shared/user.js
+++ b/resources/static/test/cases/shared/user.js
@@ -85,6 +85,12 @@ var jwcrypto = require("./lib/jwcrypto");
     equal(hostname, "persona.org", "getHostname returns only the hostname");
   });
 
+  test("setReturnTo, getReturnTo", function() {
+    var returnTo = "http://samplerp.org";
+    lib.setReturnTo(returnTo);
+    equal(lib.getReturnTo(), returnTo, "get/setReturnTo work as expected");
+  });
+
   test("getStoredEmailKeypairs without key - return all identities", function() {
     var identities = lib.getStoredEmailKeypairs();
     equal("object", typeof identities, "object returned");
@@ -292,27 +298,27 @@ var jwcrypto = require("./lib/jwcrypto");
   });
 
   asyncTest("waitForUserValidation with `complete` response", function() {
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
 
     xhr.useResult("complete");
 
     lib.waitForUserValidation("registered@testuser.com", function(status) {
       equal(status, "complete", "complete response expected");
 
-      ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes");
+      ok(!storage.getReturnTo(), "staged on behalf of is cleared when validation completes");
       start();
     }, testHelpers.unexpectedXHRFailure);
   });
 
   asyncTest("waitForUserValidation with `mustAuth` response", function() {
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
 
     xhr.useResult("mustAuth");
 
     lib.waitForUserValidation("registered@testuser.com", function(status) {
       equal(status, "mustAuth", "mustAuth response expected");
 
-      ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes");
+      ok(!storage.getReturnTo(), "staged on behalf of is cleared when validation completes");
       start();
     }, testHelpers.unexpectedXHRFailure);
   });
@@ -320,12 +326,12 @@ var jwcrypto = require("./lib/jwcrypto");
   asyncTest("waitForUserValidation with `noRegistration` response", function() {
     xhr.useResult("noRegistration");
 
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
     lib.waitForUserValidation(
       "registered@testuser.com",
       testHelpers.unexpectedSuccess,
       function(status) {
-        ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared for noRegistration response");
+        ok(storage.getReturnTo(), "staged on behalf of is not cleared for noRegistration response");
         ok(status, "noRegistration", "noRegistration response causes failure");
         start();
       }
@@ -334,12 +340,12 @@ var jwcrypto = require("./lib/jwcrypto");
 
 
   asyncTest("waitForUserValidation with XHR failure", function() {
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
     lib.waitForUserValidation(
       "registered@testuser.com",
       testHelpers.unexpectedSuccess,
       function() {
-        ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared on XHR failure");
+        ok(storage.getReturnTo(), "staged on behalf of is not cleared on XHR failure");
         ok(true, "xhr failure should always be a failure");
         start();
       }
@@ -349,7 +355,7 @@ var jwcrypto = require("./lib/jwcrypto");
   asyncTest("cancelUserValidation: ~1 second", function() {
     xhr.useResult("pending");
 
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
     // yes, we are neither expected succes nor failure because we are
     // cancelling the wait.
     lib.waitForUserValidation(
@@ -360,25 +366,25 @@ var jwcrypto = require("./lib/jwcrypto");
 
     setTimeout(function() {
       lib.cancelUserValidation();
-      ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared when validation cancelled");
+      ok(storage.getReturnTo(), "staged on behalf of is not cleared when validation cancelled");
       start();
     }, 500);
   });
 
-  asyncTest("tokenInfo with a good token and origin info, expect origin in results", function() {
-    storage.setStagedOnBehalfOf(testOrigin);
+  asyncTest("tokenInfo with a good token and returnTo info, expect returnTo in results", function() {
+    storage.setReturnTo(testOrigin);
 
     lib.tokenInfo("token", function(info) {
       equal(info.email, TEST_EMAIL, "correct email");
-      equal(info.origin, testOrigin, "correct origin");
+      equal(info.returnTo, testOrigin, "correct returnTo");
       start();
     }, testHelpers.unexpectedXHRFailure);
   });
 
-  asyncTest("tokenInfo with a bad token without site info, no site in results", function() {
+  asyncTest("tokenInfo with a bad token without returnTo info, no returnTo in results", function() {
     lib.tokenInfo("token", function(info) {
       equal(info.email, TEST_EMAIL, "correct email");
-      equal(typeof info.origin, "undefined", "origin is undefined");
+      equal(typeof info.returnTo, "undefined", "returnTo is undefined");
       start();
     }, testHelpers.unexpectedXHRFailure);
   });
@@ -388,14 +394,14 @@ var jwcrypto = require("./lib/jwcrypto");
   });
 
   asyncTest("verifyUser with a good token", function() {
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
 
     lib.verifyUser("token", "password", function onSuccess(info) {
 
       ok(info.valid, "token was valid");
       equal(info.email, TEST_EMAIL, "email part of info");
-      equal(info.origin, testOrigin, "origin in info");
-      equal(storage.getStagedOnBehalfOf(), "", "initiating origin was removed");
+      equal(info.returnTo, testOrigin, "returnTo in info");
+      equal(storage.getReturnTo(), "", "initiating origin was removed");
 
       start();
     }, testHelpers.unexpectedXHRFailure);
@@ -461,8 +467,13 @@ var jwcrypto = require("./lib/jwcrypto");
   });
 
   asyncTest("requestPasswordReset with known email - true status", function() {
+    var returnTo = "http://samplerp.org";
+    lib.setReturnTo(returnTo);
+
     lib.requestPasswordReset("registered@testuser.com", "password", function(status) {
       equal(status.success, true, "password reset for known user");
+      equal(storage.getReturnTo(), returnTo, "RP URL is stored for verification");
+
       start();
     }, testHelpers.unexpectedXHRFailure);
   });
@@ -642,13 +653,16 @@ var jwcrypto = require("./lib/jwcrypto");
   });
 
   asyncTest("addEmail", function() {
+    var returnTo = "http://samplerp.org";
+    lib.setReturnTo(returnTo);
+
     lib.addEmail("testemail@testemail.com", "password", function(added) {
       ok(added, "user was added");
 
       var identities = lib.getStoredEmailKeypairs();
-      equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation.");
+      equal("testemail@testemail.com" in identities, false, "new email is not added until confirmation.");
 
-      equal(storage.getStagedOnBehalfOf(), lib.getHostname(), "initiatingOrigin is stored");
+      equal(storage.getReturnTo(), returnTo, "RP URL is stored for verification");
 
       start();
     }, testHelpers.unexpectedXHRFailure);
@@ -663,7 +677,7 @@ var jwcrypto = require("./lib/jwcrypto");
       var identities = lib.getStoredEmailKeypairs();
       equal(false, "testemail@testemail.com" in identities, "Our new email is not added until confirmation.");
 
-      equal(typeof storage.getStagedOnBehalfOf(), "undefined", "initiatingOrigin is not stored");
+      equal(typeof storage.getReturnTo(), "undefined", "initiatingOrigin is not stored");
 
       start();
     }, testHelpers.unexpectedXHRFailure);
@@ -675,36 +689,36 @@ var jwcrypto = require("./lib/jwcrypto");
 
 
  asyncTest("waitForEmailValidation `complete` response", function() {
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
 
     xhr.useResult("complete");
     lib.waitForEmailValidation("registered@testuser.com", function(status) {
-      ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes");
+      ok(!storage.getReturnTo(), "staged on behalf of is cleared when validation completes");
       equal(status, "complete", "complete response expected");
       start();
     }, testHelpers.unexpectedXHRFailure);
   });
 
   asyncTest("waitForEmailValidation `mustAuth` response", function() {
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
     xhr.useResult("mustAuth");
 
     lib.waitForEmailValidation("registered@testuser.com", function(status) {
-      ok(!storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes");
+      ok(!storage.getReturnTo(), "staged on behalf of is cleared when validation completes");
       equal(status, "mustAuth", "mustAuth response expected");
       start();
     }, testHelpers.unexpectedXHRFailure);
   });
 
   asyncTest("waitForEmailValidation with `noRegistration` response", function() {
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
     xhr.useResult("noRegistration");
 
     lib.waitForEmailValidation(
       "registered@testuser.com",
       testHelpers.unexpectedSuccess,
       function(status) {
-        ok(storage.getStagedOnBehalfOf(), "staged on behalf of is cleared when validation completes");
+        ok(storage.getReturnTo(), "staged on behalf of is cleared when validation completes");
         ok(status, "noRegistration", "noRegistration response causes failure");
         start();
       });
@@ -712,7 +726,7 @@ var jwcrypto = require("./lib/jwcrypto");
 
 
  asyncTest("waitForEmailValidation XHR failure", function() {
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
     xhr.useResult("ajaxError");
 
     lib.waitForEmailValidation(
@@ -726,7 +740,7 @@ var jwcrypto = require("./lib/jwcrypto");
   asyncTest("cancelEmailValidation: ~1 second", function() {
     xhr.useResult("pending");
 
-    storage.setStagedOnBehalfOf(testOrigin);
+    storage.setReturnTo(testOrigin);
     lib.waitForEmailValidation(
       "registered@testuser.com",
       testHelpers.unexpectedSuccess,
@@ -735,19 +749,19 @@ var jwcrypto = require("./lib/jwcrypto");
 
     setTimeout(function() {
       lib.cancelUserValidation();
-      ok(storage.getStagedOnBehalfOf(), "staged on behalf of is not cleared when validation cancelled");
+      ok(storage.getReturnTo(), "staged on behalf of is not cleared when validation cancelled");
       start();
     }, 500);
   });
 
-  asyncTest("verifyEmail with a good token - callback with email, origin, valid", function() {
-    storage.setStagedOnBehalfOf(testOrigin);
+  asyncTest("verifyEmail with a good token - callback with email, returnTo, valid", function() {
+    storage.setReturnTo(testOrigin);
     lib.verifyEmail("token", "password", function onSuccess(info) {
 
       ok(info.valid, "token was valid");
       equal(info.email, TEST_EMAIL, "email part of info");
-      equal(info.origin, testOrigin, "origin in info");
-      equal(storage.getStagedOnBehalfOf(), "", "initiating origin was removed");
+      equal(info.returnTo, testOrigin, "returnTo in info");
+      equal(storage.getReturnTo(), "", "initiating returnTo was removed");
 
       start();
     }, testHelpers.unexpectedXHRFailure);
@@ -1199,7 +1213,7 @@ var jwcrypto = require("./lib/jwcrypto");
 
   asyncTest("shouldAskIfUsersComputer with user who has not been asked and has verified email in this dialog session - call onSuccess with false", function() {
     lib.authenticate(TEST_EMAIL, "password", function() {
-      storage.setStagedOnBehalfOf(testOrigin);
+      storage.setReturnTo(testOrigin);
       xhr.useResult("complete");
 
       lib.waitForEmailValidation(TEST_EMAIL, function() {
diff --git a/resources/views/add_email_address.ejs b/resources/views/add_email_address.ejs
index 22d36ab73ce9ab150a89ad7827c32805f2c2efa5..9a4128aeee67fac5702ae45de569cd952d41fe4a 100644
--- a/resources/views/add_email_address.ejs
+++ b/resources/views/add_email_address.ejs
@@ -60,11 +60,15 @@
 
         <div id="congrats">
             <p>
-                <%- gettext('<strong class="email">Your address</strong> has been verified!') %>
+              <%- gettext('<strong class="email">Your address</strong> has been verified!') %>
+            </p>
+
+            <p class="siteinfo">
+              <%- format(gettext('Your new address is set up and ready to go. You will be redirected to %s'), ["<strong class='website'></strong>"]) %>
+            </p>
 
-                <p class="siteinfo">
-                  <%= gettext('Your new address is set up and you should now be signed in. You may now close this window and go back to') %> <strong class="website"></strong>
-                </p>
+            <p id="redirection" class="siteinfo">
+              <%- format(gettext("Redirecting in %s seconds"), ["<span id='redirectTimeout'></span>" ]) %>
             </p>
         </div>
     </div>
diff --git a/resources/views/verify_email_address.ejs b/resources/views/verify_email_address.ejs
index cf38f704a28620bedf40bebb932813e7b812d07e..d033b9417e2b39158d875138bb6ec24aca1a0286 100644
--- a/resources/views/verify_email_address.ejs
+++ b/resources/views/verify_email_address.ejs
@@ -62,7 +62,11 @@
             </p>
 
             <p class="siteinfo">
-              <%= gettext('Your new address is set up and you should now be signed in. You may now close this window and go back to') %> <strong class="website"></strong>
+              <%- format(gettext('Your new address is set up and ready to go. You will be redirected to %s'), ["<strong class='website'></strong>"]) %>
+            </p>
+
+            <p id="redirection" class="siteinfo">
+              <%- format(gettext("Redirecting in %s seconds"), ["<span id='redirectTimeout'></span>" ]) %>
             </p>
         </div>