diff --git a/resources/static/dialog/controllers/dialog.js b/resources/static/dialog/controllers/dialog.js
index b64dcbf422e5a04a4ecfadbc109b859116f6d266..028f2ff6418cb2298ccf5c6f5e0b0db789d6c774 100644
--- a/resources/static/dialog/controllers/dialog.js
+++ b/resources/static/dialog/controllers/dialog.js
@@ -146,6 +146,7 @@ BrowserID.Modules.Dialog = (function() {
 
         params.hostname = user.getHostname();
 
+        // XXX Perhaps put this into the state machine.
         self.bind(win, "unload", onWindowUnload);
 
         self.publish("start", params);
diff --git a/resources/static/dialog/controllers/verify_primary_user.js b/resources/static/dialog/controllers/verify_primary_user.js
index 2725849454c3e8531eaf96869170d93157a42614..d023c03ece468d9f2b52874e83ab51bb8831f7d5 100644
--- a/resources/static/dialog/controllers/verify_primary_user.js
+++ b/resources/static/dialog/controllers/verify_primary_user.js
@@ -38,19 +38,35 @@ BrowserID.Modules.VerifyPrimaryUser = (function() {
   "use strict";
 
   var bid = BrowserID,
-      sc;
+      sc,
+      win,
+      email,
+      auth_url;
+
+  function verify(callback) {
+    this.publish("primary_verifying_user");
+
+    var url = auth_url + "?email=" + encodeURIComponent(email);
+    win.document.location = url;
+
+    callback();
+  }
 
   var Module = bid.Modules.PageModule.extend({
     start: function(data) {
       var self=this;
-
       data = data || {};
 
-      // XXX YEEHA!  We've gotta set some variables and redirect off to the
-      // IdPs page.
-      sc.start.call(this, data);
-    }
+      win = data.window || window;
+      email = data.email;
+      auth_url = data.auth_url;
+
+      self.renderDialog("verifyWithPrimary", data);
+
+      sc.start.call(self, data);
+    },
 
+    submit: verify
   });
 
   sc = Module.sc;
diff --git a/resources/static/dialog/resources/state_machine.js b/resources/static/dialog/resources/state_machine.js
index 00f401da5f9927efbbaa3a4dc054b139fd4b9a6d..c7fc2a42d40da52507a93fe5168437b154e49166 100644
--- a/resources/static/dialog/resources/state_machine.js
+++ b/resources/static/dialog/resources/state_machine.js
@@ -154,6 +154,12 @@
       gotoState("doVerifyPrimaryUser", info);
     });
 
+    subscribe("primary_verifying_user", function(msg, info) {
+      // Keep the dialog from automatically closing when the user browses to
+      // the IdP for verification.
+      self.success = true;
+    });
+
     subscribe("authenticate_with_required_email", function(msg, info) {
       gotoState("doAuthenticateWithRequiredEmail", info);
     });
diff --git a/resources/static/dialog/views/verifyWithPrimary.ejs b/resources/static/dialog/views/verifyWithPrimary.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..8a29a4ba881255a9d0765b361e624b03e3473098
--- /dev/null
+++ b/resources/static/dialog/views/verifyWithPrimary.ejs
@@ -0,0 +1,16 @@
+  <strong>Verify With Email Provider</strong>
+
+  <div id="addEmail" class="cf form_section">
+    To verify that you own <strong id="primary_email"><%= email %></strong>, you must
+    sign in with your provider.  This window will be redirected to
+
+    <p>
+      <strong><%= auth_url %></strong>.
+    </p>
+
+    <div class="submit cf">
+      <button id="verifyWithPrimary">Verify</button>
+    </div>
+
+  </div>
+
diff --git a/resources/static/pages/signup.js b/resources/static/pages/signup.js
index 0613649254d78d265dd70e627e5db3cfd5b44bdb..d293f71aba2a93c52ac0d786c6c7ae57fba006b9 100644
--- a/resources/static/pages/signup.js
+++ b/resources/static/pages/signup.js
@@ -61,7 +61,7 @@ BrowserID.signUp = (function() {
       }
 
       var url = verifyURL + "?email=" + encodeURIComponent(verifyEmail);
-      win.open(url, "_moz_primary_verification", "width: 500px, height: 500px");
+      win.open(url, "_moz_primary_verification", "width=500, height=500");
       oncomplete && oncomplete();
     }
 
diff --git a/resources/static/test/index.html b/resources/static/test/index.html
index 9a990769264f68e87b75949ca961509d33681b81..fcdc7b41899b8b18d3358a157e2bbe82387a5535 100644
--- a/resources/static/test/index.html
+++ b/resources/static/test/index.html
@@ -91,6 +91,7 @@
     <script type="text/javascript" src="qunit/mocks/xhr.js"></script>
     <script type="text/javascript" src="qunit/mocks/templates.js"></script>
     <script type="text/javascript" src="qunit/mocks/provisioning.js"></script>
+    <script type="text/javascript" src="qunit/mocks/window.js"></script>
     <script type="text/javascript" src="/shared/javascript-extensions.js"></script>
     <script type="text/javascript" src="/shared/renderer.js"></script>
     <script type="text/javascript" src="/shared/class.js"></script>
diff --git a/resources/static/test/qunit/controllers/verify_primary_user_unit_test.js b/resources/static/test/qunit/controllers/verify_primary_user_unit_test.js
index 1a9390b0530be4443392dd51ff12f26b0a03a468..eb8a40722ef99d68188c58c3c1f5b85758c250c9 100644
--- a/resources/static/test/qunit/controllers/verify_primary_user_unit_test.js
+++ b/resources/static/test/qunit/controllers/verify_primary_user_unit_test.js
@@ -40,7 +40,10 @@
   var bid = BrowserID,
       controller,
       el,
-      testHelpers = bid.TestHelpers;
+      testHelpers = bid.TestHelpers,
+      WindowMock = bid.Mocks.WindowMock,
+      win,
+      mediator = bid.Mediator;
 
   function createController(config) {
     controller = BrowserID.Modules.VerifyPrimaryUser.create();
@@ -50,6 +53,12 @@
   module("controllers/verify_primary_user", {
     setup: function() {
       testHelpers.setup();
+      win = new WindowMock();
+      createController({
+        window: win,
+        email: "unregistered@testuser.com",
+        auth_url: "http://testuser.com/sign_in"
+      });
     },
 
     teardown: function() {
@@ -60,6 +69,17 @@
     }
   });
 
-  // XXX Do some tests!
+  asyncTest("submit opens a new tab with correct URL", function() {
+    var messageTriggered = false;
+    mediator.subscribe("primary_verifying_user", function() {
+      messageTriggered = true;
+    });
+
+    controller.submit(function() {
+      equal(win.document.location, "http://testuser.com/sign_in?email=unregistered%40testuser.com");
+      equal(messageTriggered, true, "primary_verifying_user triggered");
+      start();
+    });
+  });
 }());
 
diff --git a/resources/static/test/qunit/mocks/window.js b/resources/static/test/qunit/mocks/window.js
new file mode 100644
index 0000000000000000000000000000000000000000..2728fb434bd033d5f714dfe88d223300f7a10866
--- /dev/null
+++ b/resources/static/test/qunit/mocks/window.js
@@ -0,0 +1,55 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global BrowserID: true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+BrowserID.Mocks.WindowMock = (function() {
+  "use strict";
+
+  function DocumentMock() {
+    this.location = document.location;
+  }
+
+  function WindowMock() {
+    this.document = new DocumentMock();
+  }
+  WindowMock.prototype = {
+    open: function(url, name, options) {
+      this.open_url = url;
+    }
+  };
+
+  return WindowMock;
+
+}());
diff --git a/resources/static/test/qunit/pages/signup_unit_test.js b/resources/static/test/qunit/pages/signup_unit_test.js
index 08efadc86917f5688d6953a92a610d8bee239d0f..12c2f17cdf6dd3079ba058319a3fff43faf17d96 100644
--- a/resources/static/test/qunit/pages/signup_unit_test.js
+++ b/resources/static/test/qunit/pages/signup_unit_test.js
@@ -40,23 +40,11 @@
   var bid = BrowserID,
       network = bid.Network,
       xhr = bid.Mocks.xhr,
+      WindowMock = bid.Mocks.WindowMock,
       testHelpers = bid.TestHelpers,
       provisioning = bid.Mocks.Provisioning,
       win;
 
-  function DocumentMock() {
-    this.location = document.location;
-  }
-
-  function WindowMock() {
-    this.document = new DocumentMock();
-  }
-  WindowMock.prototype = {
-    open: function(url, name, options) {
-      this.open_url = url;
-    }
-  };
-
   module("pages/signup", {
     setup: function() {
       testHelpers.setup();