diff --git a/.gitignore b/.gitignore
index 73041fa172458844ed9fb63376a65e3893929796..d92c19c88b771f93c86183ed55afa7be9be09ba8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,3 @@
-*.sekret
-*.sqlite
-*.log
 *~
 \#*\#
 .\#*
diff --git a/ORGANIZATION.md b/ORGANIZATION.md
new file mode 100644
index 0000000000000000000000000000000000000000..a7b12c8b35288e5fdbdefe0a0e1fa907ff39d4c4
--- /dev/null
+++ b/ORGANIZATION.md
@@ -0,0 +1,17 @@
+Several node.js servers are implemented in this repostiory, each is
+implemented on top of the [express](http://expressjs.com) framework
+and should obey roughly the following directory structure:
+
+  * `var/` - a demand created directory with ephemeral files generated
+             during the run (keys, logs, etc).
+  * `static/` - files served verbatim without any substitution nor server
+             side logic in them
+  * `lib/` - javascript modules.
+  * `views/` - express views, served before `static/` (if present)
+  * `tests/` - tests written using [vows](http://vowsjs.org)
+  * `tests/run.js` - a "run all" script to run all tests
+  * `app.js` application "entry point", exposes a single function `exports.setup`
+    that takes a handle to an express server as an argument and sets up routes
+    or associates middleware to it.
+  * `run.js` - script to run a standalone (production) node.js server - typically bound
+     against a well known localhost port.
diff --git a/README.md b/README.md
index 86a4a8dc648773476cfa6fbba5d67f8e6a7b15c1..6a4d5d34985171a3e4995de6718d2c3d4ff525f2 100644
--- a/README.md
+++ b/README.md
@@ -6,12 +6,13 @@ This is an exploration of the distributed identity system
 All of the servers here are based on node.js, and some number of 3rd party node modules are required to make them go.  ([npm](http://npmjs.org/) is a good way to get these libraries)
 
 * node.js (>= 0.4.5): http://nodejs.org/
-* connect (>= 1.3.0): http://senchalabs.github.com/connect/
+* express (>= 2.3.11): http://senchalabs.github.com/express/
 * xml2js (>= 0.1.5)
 * sqlite (>= 1.0.3)
 * mustache (>= 0.3.1)
 * cookie-sessions (>= 0.0.2)
 * nodemailer (>= 0.1.18)
+* emavows (>= 0.5.8)
 
 ## Getting started:
 
diff --git a/authority/.gitignore b/authority/.gitignore
deleted file mode 100644
index ae9f5955b3f2463d8aa275899dfa93fe2d05c42b..0000000000000000000000000000000000000000
--- a/authority/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/authdb.sqlite
diff --git a/authority/server/prove_template.txt b/authority/server/prove_template.txt
deleted file mode 100644
index fd6dea3030f0fa66bbfab6b1dd80c4ef675164c6..0000000000000000000000000000000000000000
--- a/authority/server/prove_template.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-Hello {{email}},
- 
-This message is being sent to you because someone is trying to log
-into a site using this email address.  If you are presently logging
-into a website using BrowserID, then click the link below to complete
-your login:
- 
-{{link}}
- 
-If you are NOT trying to log into a website, just delete this email.
- 
-Sincerely,
-BrowserID (A better way to login)
diff --git a/authority/server/standalone.js b/authority/server/standalone.js
deleted file mode 100644
index 7563c90864caa847649855d7bdd1a4fd15283aa9..0000000000000000000000000000000000000000
--- a/authority/server/standalone.js
+++ /dev/null
@@ -1,26 +0,0 @@
-var   sys = require("sys"),
-     http = require("http"),
-      url = require("url"),
-     path = require("path"),
-       fs = require("fs"),
-  connect = require("connect");
-
-var PRIMARY_HOST = "127.0.0.1";
-var PRIMARY_PORT = 62700;
-
-var handler = require("./run.js");
-
-var server = connect.createServer().use(connect.favicon())
-    .use(connect.logger({
-        stream: fs.createWriteStream(path.join(__dirname, "server.log"))
-    }));
-
-// let the specific server interact directly with the connect server to register their middleware
-if (handler.setup) handler.setup(server);
-
-server.use(handler.handler);
-
-// use the connect 'static' middleware for serving of static files (cache headers, HTTP range, etc)
-server.use(connect.static(path.join(path.dirname(__dirname), "static")));
-
-server.listen(PRIMARY_PORT, PRIMARY_HOST);
diff --git a/browserid/.gitignore b/browserid/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c4c53c804943031be0c755915d85c53e73beca7b
--- /dev/null
+++ b/browserid/.gitignore
@@ -0,0 +1 @@
+/var
diff --git a/browserid/app.js b/browserid/app.js
new file mode 100644
index 0000000000000000000000000000000000000000..3b1fbd49ab3fb8ad2062348a1319aaafc8dadd21
--- /dev/null
+++ b/browserid/app.js
@@ -0,0 +1,89 @@
+const        path = require('path'),
+              url = require('url'),
+               fs = require('fs'),
+            wsapi = require('./lib/wsapi.js'),
+        httputils = require('./lib/httputils.js'),
+        webfinger = require('./lib/webfinger.js'),
+         sessions = require('cookie-sessions'),
+          express = require('express'),
+          secrets = require('./lib/secrets.js');
+
+// create the var directory if it doesn't exist
+var VAR_DIR = path.join(__dirname, "var");
+try { fs.mkdirSync(VAR_DIR, 0755); } catch(e) { }
+
+const STATIC_DIR = path.join(path.dirname(__dirname), "static");
+
+const COOKIE_SECRET = secrets.hydrateSecret('cookie_secret', VAR_DIR);
+
+const COOKIE_KEY = 'browserid_state';
+
+function handler(request, response, next) {
+    // dispatch!
+    var urlpath = url.parse(request.url).pathname;
+
+    if (urlpath === '/sign_in') {
+        // a little remapping!
+        request.url = "/dialog/index.html";
+        next();
+    } else if (urlpath === '/register_iframe') {
+        request.url = "/dialog/register_iframe.html";
+        next();
+    } else if (/^\/wsapi\/\w+$/.test(urlpath)) {
+        try {
+            var method = path.basename(urlpath);
+            wsapi[method](request, response);
+        } catch(e) {
+            var errMsg = "oops, error executing wsapi method: " + method + " (" + e.toString() +")";
+            console.log(errMsg);
+            httputils.fourOhFour(response, errMsg);
+        }
+    } else if (/^\/users\/[^\/]+.xml$/.test(urlpath)) {
+        var identity = path.basename(urlpath).replace(/.xml$/, '').replace(/^acct:/, '');
+
+        webfinger.renderUserPage(identity, function (resultDocument) {
+            if (resultDocument === undefined) {
+                httputils.fourOhFour(response, "I don't know anything about: " + identity + "\n");
+            } else {
+                httputils.xmlResponse(response, resultDocument);
+            }
+        });
+    } else if (urlpath === "/code_update") {
+        console.log("code updated.  shutting down.");
+        process.exit();
+    } else {
+        next();
+    }
+};
+
+exports.varDir = VAR_DIR;
+
+exports.setup = function(server) {
+    server.use(express.cookieParser());
+
+    var cookieSessionMiddleware = sessions({
+        secret: COOKIE_SECRET,
+        session_key: COOKIE_KEY,
+        path: '/'
+    });
+
+    server.use(function(req, resp, next) {
+        try {
+            cookieSessionMiddleware(req, resp, next);
+        } catch(e) {
+            console.log("invalid cookie found: ignoring");
+            delete req.cookies[COOKIE_KEY];
+            cookieSessionMiddleware(req, resp, next);
+        }
+    });
+
+    server.use(handler);
+
+    // a tweak to get the content type of host-meta correct
+    server.use(function(req, resp, next) {
+        if (req.url === '/.well-known/host-meta') {
+            resp.setHeader('content-type', 'text/xml');
+        }
+        next();
+    });
+}
diff --git a/authority/server/db.js b/browserid/lib/db.js
similarity index 99%
rename from authority/server/db.js
rename to browserid/lib/db.js
index 2c4275d09c0cf570a0e6b25ef82550a7e95df4e2..97d81475367066cc4b11da1d56a26a75394773f0 100644
--- a/authority/server/db.js
+++ b/browserid/lib/db.js
@@ -3,7 +3,7 @@ const sqlite = require('sqlite'),
 
 var db = new sqlite.Database();
 
-db.open(path.join(path.dirname(__dirname), "authdb.sqlite"), function (error) {
+db.open(path.join(path.dirname(__dirname), "var", "authdb.sqlite"), function (error) {
   if (error) {
     console.log("Couldn't open database: " + error);
     throw error;
diff --git a/authority/server/email.js b/browserid/lib/email.js
similarity index 63%
rename from authority/server/email.js
rename to browserid/lib/email.js
index 59a01aa96b3f4a1179742a5df9bbfb72dfc6a75d..bc5fa584cdc0aaccdbd26c0f4c29b540e52415c5 100644
--- a/authority/server/email.js
+++ b/browserid/lib/email.js
@@ -6,15 +6,18 @@ mustache = require('mustache');
 
 const template = fs.readFileSync(path.join(__dirname, "prove_template.txt")).toString();
 
-exports.sendVerificationEmail = function(email, secret) {
+exports.sendVerificationEmail = function(email, site, secret) {
     var url = "https://browserid.org/prove.html?token=" + secret;
 
     emailer.send_mail({
         sender: "noreply@browserid.org",
         to: email,
-        subject : "BrowserID: confirm email address - " + email,
-        body: mustache.to_html(template, { email: email, link: url })
+        subject : "Complete Login to " + site + " using BrowserID",
+        body: mustache.to_html(template, { email: email, link: url, site: site })
     }, function(err, success){
-        if(!success) console.log("error sending email: ", err);
+        if(!success) {
+            console.log("error sending email: ", err);
+            console.log("verification URL: ", url);
+        }
     });
 };
diff --git a/authority/server/httputils.js b/browserid/lib/httputils.js
similarity index 100%
rename from authority/server/httputils.js
rename to browserid/lib/httputils.js
diff --git a/browserid/lib/prove_template.txt b/browserid/lib/prove_template.txt
new file mode 100644
index 0000000000000000000000000000000000000000..09a7d7c8e4acb7987d05ab014098d57025535f10
--- /dev/null
+++ b/browserid/lib/prove_template.txt
@@ -0,0 +1,12 @@
+Hi {{email}},
+ 
+This message is being sent to you to complete your login to
+{{site}}.  Click the link below to continue:
+ 
+{{link}}
+ 
+If you are NOT trying to log into {{site}}, just ignore this email. 
+ 
+Thanks,
+BrowserID
+(A better way to login)
diff --git a/authority/server/secrets.js b/browserid/lib/secrets.js
similarity index 100%
rename from authority/server/secrets.js
rename to browserid/lib/secrets.js
diff --git a/authority/server/webfinger.js b/browserid/lib/webfinger.js
similarity index 100%
rename from authority/server/webfinger.js
rename to browserid/lib/webfinger.js
diff --git a/authority/server/webfinger_template.xml b/browserid/lib/webfinger_template.xml
similarity index 100%
rename from authority/server/webfinger_template.xml
rename to browserid/lib/webfinger_template.xml
diff --git a/authority/server/wsapi.js b/browserid/lib/wsapi.js
similarity index 96%
rename from authority/server/wsapi.js
rename to browserid/lib/wsapi.js
index 1a900b85ffa1f82d4b8da0bd32f124a3d8089713..aafd2d1eec96af90e3a7beca1bfc168535b32c54 100644
--- a/authority/server/wsapi.js
+++ b/browserid/lib/wsapi.js
@@ -55,7 +55,9 @@ exports.stage_user = function(req, resp) {
   var urlobj = url.parse(req.url, true);
   var getArgs = urlobj.query;
 
-  if (!checkParams(getArgs, resp, [ "email", "pass", "pubkey" ])) return;
+  if (!checkParams(getArgs, resp, [ "email", "pass", "pubkey", "site" ])) {
+    return;
+  }
 
   logRequest("stage_user", getArgs);
 
@@ -71,7 +73,8 @@ exports.stage_user = function(req, resp) {
     httputils.jsonResponse(resp, true);
 
     // let's now kick out a verification email!
-    email.sendVerificationEmail(getArgs.email, secret);
+    email.sendVerificationEmail(getArgs.email, getArgs.site, secret);
+
   } catch(e) {
     // we should differentiate tween' 400 and 500 here.
     httputils.badRequest(resp, e.toString());
@@ -123,7 +126,7 @@ exports.add_email = function (req, resp) {
   var urlobj = url.parse(req.url, true);
   var getArgs = urlobj.query;
 
-  if (!checkParams(getArgs, resp, [ "email", "pubkey" ])) return;
+  if (!checkParams(getArgs, resp, [ "email", "pubkey", "site" ])) return;
 
   if (!checkAuthed(req, resp)) return;
 
@@ -140,7 +143,7 @@ exports.add_email = function (req, resp) {
     httputils.jsonResponse(resp, true);
 
     // let's now kick out a verification email!
-    email.sendVerificationEmail(getArgs.email, secret);
+    email.sendVerificationEmail(getArgs.email, getArgs.site, secret);
   } catch(e) {
     // we should differentiate tween' 400 and 500 here.
     httputils.badRequest(resp, e.toString());
diff --git a/browserid/run.js b/browserid/run.js
new file mode 100755
index 0000000000000000000000000000000000000000..564a6afc37b1256792b15139ccba1b799a4b1e91
--- /dev/null
+++ b/browserid/run.js
@@ -0,0 +1,24 @@
+#!/usr/bin/env node
+
+var  path = require("path"),
+       fs = require("fs"),
+  express = require("express");
+
+var PRIMARY_HOST = "127.0.0.1";
+var PRIMARY_PORT = 62700;
+
+var handler = require("./app.js");
+
+var app = express.createServer();
+
+app.use(express.logger({
+    stream: fs.createWriteStream(path.join(handler.varDir, "server.log"))
+}));
+
+// let the specific server interact directly with the connect server to register their middleware
+if (handler.setup) handler.setup(app);
+
+// use the express 'static' middleware for serving of static files (cache headers, HTTP range, etc)
+app.use(express.static(path.join(__dirname, "static")));
+
+app.listen(PRIMARY_PORT, PRIMARY_HOST);
diff --git a/authority/static/.well-known/host-meta b/browserid/static/.well-known/host-meta
similarity index 100%
rename from authority/static/.well-known/host-meta
rename to browserid/static/.well-known/host-meta
diff --git a/authority/static/css/github.css b/browserid/static/css/github.css
similarity index 100%
rename from authority/static/css/github.css
rename to browserid/static/css/github.css
diff --git a/authority/static/css/sil.ttf b/browserid/static/css/sil.ttf
similarity index 100%
rename from authority/static/css/sil.ttf
rename to browserid/static/css/sil.ttf
diff --git a/authority/static/css/style.css b/browserid/static/css/style.css
similarity index 100%
rename from authority/static/css/style.css
rename to browserid/static/css/style.css
diff --git a/authority/static/css/ts.ttf b/browserid/static/css/ts.ttf
similarity index 100%
rename from authority/static/css/ts.ttf
rename to browserid/static/css/ts.ttf
diff --git a/authority/static/developers.html b/browserid/static/developers.html
similarity index 100%
rename from authority/static/developers.html
rename to browserid/static/developers.html
diff --git a/authority/static/dialog/crypto-stubs.js b/browserid/static/dialog/crypto-stubs.js
similarity index 100%
rename from authority/static/dialog/crypto-stubs.js
rename to browserid/static/dialog/crypto-stubs.js
diff --git a/authority/static/dialog/crypto.js b/browserid/static/dialog/crypto.js
similarity index 100%
rename from authority/static/dialog/crypto.js
rename to browserid/static/dialog/crypto.js
diff --git a/authority/static/dialog/index.html b/browserid/static/dialog/index.html
similarity index 93%
rename from authority/static/dialog/index.html
rename to browserid/static/dialog/index.html
index 91e0202e29c97784aac34245a3b4ae2b0b5eef17..209c90b7183c57588f45c111e3797341b23154cf 100644
--- a/authority/static/dialog/index.html
+++ b/browserid/static/dialog/index.html
@@ -29,7 +29,8 @@
 </div>
 <div id="create_dialog" class="dialog">
   <div class="content">
-    <div class="summary">BrowserID makes logging in <b>safer and easier</b>.  To begin, please provide an email address and pick a password:</div>
+    <div class="summary create">BrowserID makes logging in <b>safer and easier</b>.  To begin, please provide an email address and pick a password:</div>
+    <div class="summary forgot"><b>Forgot your password?</b>  No problem!  Enter your email address, pick a new password, and we'll get you set up again!</div>
     <div class="input">
       <div class="label"> Email </div>
       <div class="input"> <input type="text"></input></div>
diff --git a/authority/static/dialog/jquery-min.js b/browserid/static/dialog/jquery-min.js
similarity index 100%
rename from authority/static/dialog/jquery-min.js
rename to browserid/static/dialog/jquery-min.js
diff --git a/authority/static/dialog/jschannel.js b/browserid/static/dialog/jschannel.js
similarity index 100%
rename from authority/static/dialog/jschannel.js
rename to browserid/static/dialog/jschannel.js
diff --git a/authority/static/dialog/main.js b/browserid/static/dialog/main.js
similarity index 90%
rename from authority/static/dialog/main.js
rename to browserid/static/dialog/main.js
index d3167729c87bd3d0c3805559a350605778450aeb..e1111069749edc8bcb812503914d5f7c5ea81a48 100644
--- a/authority/static/dialog/main.js
+++ b/browserid/static/dialog/main.js
@@ -247,10 +247,10 @@
     }
 
     $("#authenticate_dialog div.note > a").unbind('click').click(function() {
-      onerror("notImplemented");
+      runCreateDialog(true, onsuccess, onerror);
     });
     $("#authenticate_dialog div.actions div.action").unbind('click').click(function() {
-      runCreateDialog(onsuccess, onerror);
+      runCreateDialog(false, onsuccess, onerror);
     });
 
     $("#authenticate_dialog div.attention_lame").hide();
@@ -329,7 +329,7 @@
 
     $("#back").show().unbind('click').click(function() {
       window.clearTimeout(pollTimeout);
-      runCreateDialog(onsuccess, onerror);
+      runCreateDialog(false, onsuccess, onerror);
     });
 
     $("#cancel").show().unbind('click').click(function() {
@@ -419,7 +419,9 @@
       );
 
       $.ajax({
-        url: '/wsapi/add_email?email=' + encodeURIComponent(email) + '&pubkey=' + encodeURIComponent(keypair.pub),
+        url: '/wsapi/add_email?email=' + encodeURIComponent(email)
+              + '&pubkey=' + encodeURIComponent(keypair.pub)
+              + '&site=' + encodeURIComponent(remoteOrigin.replace(/^(http|https):\/\//, '')),
         success: function() {
           // email successfully staged, now wait for email confirmation
           runConfirmEmailDialog(email, keypair, onsuccess, onerror);
@@ -449,9 +451,13 @@
     $("#add_email_dialog").fadeIn(500);
   }
 
-  function runCreateDialog(onsuccess, onerror) {
+  function runCreateDialog(forgot, onsuccess, onerror) {
     $(".dialog").hide();
 
+    // show the proper summary text
+    $("#create_dialog .content .summary").hide();
+    $("#create_dialog .content " + (forgot ? ".forgot" : ".create")).show();
+
     $("#back").show().unbind('click').click(function() {
       runAuthenticateDialog(undefined, onsuccess, onerror);
     });
@@ -476,7 +482,10 @@
       );
 
       $.ajax({
-        url: '/wsapi/stage_user?email=' + encodeURIComponent(email) + '&pass=' + encodeURIComponent(pass) + '&pubkey=' + encodeURIComponent(keypair.pub),
+        url: '/wsapi/stage_user?email=' + encodeURIComponent(email)
+              + '&pass=' + encodeURIComponent(pass)
+              + '&pubkey=' + encodeURIComponent(keypair.pub)
+              + '&site=' + encodeURIComponent(remoteOrigin.replace(/^(http|https):\/\//, '')),
         success: function() {
           // account successfully staged, now wait for email confirmation
           runConfirmEmailDialog(email, keypair, onsuccess, onerror);
@@ -510,13 +519,15 @@
           // user anything, cause this is a non-critical issue
 
         } else if (typeof valid === 'boolean') {
-          if (valid) {
-            $("#create_dialog div.note:eq(0)").html($('<span class="good"/>').text("Not registered"));
-            $("#create_dialog div.attention_lame").hide();
-          } else {
-            $("#create_dialog div.attention_lame").fadeIn(300);
-            $("#create_dialog div.attention_lame span.email").text(email);
-            $("#submit").addClass("disabled");
+          if (!forgot) {
+            if (valid) {
+              $("#create_dialog div.note:eq(0)").html($('<span class="good"/>').text("Not registered"));
+              $("#create_dialog div.attention_lame").hide();
+            } else {
+              $("#create_dialog div.attention_lame").fadeIn(300);
+              $("#create_dialog div.attention_lame span.email").text(email);
+              $("#submit").addClass("disabled");
+            }
           }
         } else {
           // this is an email that needs to be checked!
@@ -579,6 +590,44 @@
     $("#create_dialog").fadeIn(500);
   }
 
+  var kindaLikeEmailPat = /^.*\@.*\..*$/;
+
+  function runForgotDialog(onsuccess, onerror) {
+    $(".dialog").hide();
+
+    $("#back").show().unbind('click').click(function() {
+      runAuthenticateDialog(undefined, onsuccess, onerror);
+    });
+    $("#cancel").show().unbind('click').click(function() {
+      onerror("canceled");
+    });
+    $("#submit").show().unbind('click').click(function() {
+        // ignore the click if we're disabled
+        if ($(this).hasClass('disabled')) return true;
+        onerror("notImplemented");
+    }).text("Send Reset Email").addClass('disabled');
+
+    function checkInput() {
+        // check the email address
+        var email = $("#forgot_password_dialog input").val();
+        // if the entered text has a basic resemblance to an email, we'll
+        // unstick the submit button
+        $("#submit").removeClass('disabled')
+        if (!kindaLikeEmailPat.test(email)) $("#submit").addClass('disabled')
+    }
+
+    // watch input dialogs
+    $("#forgot_password_dialog input").unbind('keyup').bind('keyup', checkInput);
+
+    // do a check at load time, in case the user is using the back button (enables the continue button!)
+    checkInput();
+
+    $("#forgot_password_dialog").fadeIn(500);
+
+    $("#forgot_password_dialog input").focus();
+  }
+
+
   function errorOut(trans, code) {
     function getVerboseMessage(code) {
       var msgs = {
@@ -588,8 +637,8 @@
       };
       var msg = msgs[code];
       if (!msg) {
-        alert("need verbose message for " + code); 
-        msg = "unknown error"
+          alert("need verbose message for " + code);
+          msg = "unknown error"
       }
       return msg;
     }
diff --git a/authority/static/dialog/mozilla.png b/browserid/static/dialog/mozilla.png
similarity index 100%
rename from authority/static/dialog/mozilla.png
rename to browserid/static/dialog/mozilla.png
diff --git a/authority/static/dialog/register_iframe.html b/browserid/static/dialog/register_iframe.html
similarity index 100%
rename from authority/static/dialog/register_iframe.html
rename to browserid/static/dialog/register_iframe.html
diff --git a/authority/static/dialog/register_iframe.js b/browserid/static/dialog/register_iframe.js
similarity index 100%
rename from authority/static/dialog/register_iframe.js
rename to browserid/static/dialog/register_iframe.js
diff --git a/authority/static/dialog/style.css b/browserid/static/dialog/style.css
similarity index 99%
rename from authority/static/dialog/style.css
rename to browserid/static/dialog/style.css
index 7830922fbd2f634e59a22500d188bbb76410a48c..cd7881369c5bd051e14b9a015f80502fb46ba1d2 100644
--- a/authority/static/dialog/style.css
+++ b/browserid/static/dialog/style.css
@@ -209,7 +209,7 @@ div.subtle {
 div.dialog div.attention, div.dialog div.attention_awesome, div.dialog div.attention_lame  {
     -moz-border-radius: 4px;
     -webkit-border-radius: 4px;
-    border-radius: 4px;
+    border-radius: 4px 4px 4px 4px;
     width: 400px;
     padding:13px;
     margin: auto;
diff --git a/authority/static/dialog/test_crypto_stubs.htm b/browserid/static/dialog/test_crypto_stubs.htm
similarity index 100%
rename from authority/static/dialog/test_crypto_stubs.htm
rename to browserid/static/dialog/test_crypto_stubs.htm
diff --git a/authority/static/dialog/underscore-min.js b/browserid/static/dialog/underscore-min.js
similarity index 100%
rename from authority/static/dialog/underscore-min.js
rename to browserid/static/dialog/underscore-min.js
diff --git a/authority/static/favicon.ico b/browserid/static/favicon.ico
similarity index 100%
rename from authority/static/favicon.ico
rename to browserid/static/favicon.ico
diff --git a/authority/static/i/check.png b/browserid/static/i/check.png
similarity index 100%
rename from authority/static/i/check.png
rename to browserid/static/i/check.png
diff --git a/authority/static/i/developers.png b/browserid/static/i/developers.png
similarity index 100%
rename from authority/static/i/developers.png
rename to browserid/static/i/developers.png
diff --git a/authority/static/i/id.png b/browserid/static/i/id.png
similarity index 100%
rename from authority/static/i/id.png
rename to browserid/static/i/id.png
diff --git a/authority/static/i/lock.png b/browserid/static/i/lock.png
similarity index 100%
rename from authority/static/i/lock.png
rename to browserid/static/i/lock.png
diff --git a/authority/static/i/people.png b/browserid/static/i/people.png
similarity index 100%
rename from authority/static/i/people.png
rename to browserid/static/i/people.png
diff --git a/authority/static/i/sunny.png b/browserid/static/i/sunny.png
similarity index 100%
rename from authority/static/i/sunny.png
rename to browserid/static/i/sunny.png
diff --git a/authority/static/include.js b/browserid/static/include.js
similarity index 96%
rename from authority/static/include.js
rename to browserid/static/include.js
index 4bffd65457f95345f7fa5f23b1014e472d2eca87..56cc35534683b8cf5cad6c271e10a8f05b355338 100644
--- a/authority/static/include.js
+++ b/browserid/static/include.js
@@ -525,29 +525,6 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed)
     };
   })();
 
-  function JWTWrapper(jwtBlob) {
-    var obj = {
-      jwt: jwtBlob,
-      email: undefined,
-      audience: undefined,
-      issuer: undefined,
-      "valid-until": undefined,
-      toString: function() { return this.jwt; }
-    };
-    // attempt to decode the middle part of the assertion to populate object keys
-    try {
-      var jwtContents = JSON.parse(window.atob(jwtBlob.split(".")[1]));
-      for (var k in obj) {
-        if (obj.hasOwnProperty(k) && obj[k] === undefined) {
-          if (typeof jwtContents[k] === 'string' || typeof jwtContents[k] === 'number') obj[k] = jwtContents[k];
-        }
-      }
-    } catch(e) {
-      // failure is an option.
-    }
-
-    return obj;
-  }
 
   var chan = undefined;
 
@@ -582,8 +559,8 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed)
       method: "getVerifiedEmail",
       success: function(rv) {
         if (callback) {
-          // wrap the raw JWT with a handy dandy object that exposes everything in a readable form
-          callback(JWTWrapper(rv));
+          // return the string representation of the JWT, the client is responsible for unpacking it.
+          callback(rv);
         }
         cleanup();
       },
@@ -659,8 +636,8 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed)
       params: [email, token],
       success: function(rv) {
         if (onsuccess) {
-          // wrap the raw JWT with a handy dandy object that exposes everything in a readable form
-          onsuccess(JWTWrapper(rv));
+          // return the string representation of the JWT, the client is responsible for unpacking it.
+          onsuccess(rv);
         }
         cleanup();
       },
diff --git a/authority/static/index.html b/browserid/static/index.html
similarity index 100%
rename from authority/static/index.html
rename to browserid/static/index.html
diff --git a/authority/static/js/highlight.js b/browserid/static/js/highlight.js
similarity index 100%
rename from authority/static/js/highlight.js
rename to browserid/static/js/highlight.js
diff --git a/authority/static/manage.html b/browserid/static/manage.html
similarity index 100%
rename from authority/static/manage.html
rename to browserid/static/manage.html
diff --git a/authority/static/ping.txt b/browserid/static/ping.txt
similarity index 100%
rename from authority/static/ping.txt
rename to browserid/static/ping.txt
diff --git a/authority/static/primaries.html b/browserid/static/primaries.html
similarity index 100%
rename from authority/static/primaries.html
rename to browserid/static/primaries.html
diff --git a/authority/static/prove.html b/browserid/static/prove.html
similarity index 100%
rename from authority/static/prove.html
rename to browserid/static/prove.html
diff --git a/authority/static/users.html b/browserid/static/users.html
similarity index 100%
rename from authority/static/users.html
rename to browserid/static/users.html
diff --git a/primary/.gitignore b/primary/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c4c53c804943031be0c755915d85c53e73beca7b
--- /dev/null
+++ b/primary/.gitignore
@@ -0,0 +1 @@
+/var
diff --git a/authority/server/run.js b/primary/app.js
similarity index 62%
rename from authority/server/run.js
rename to primary/app.js
index 1c24d5bfacf10ec75891688902ecc0d883cf0553..9aee24b82fa72d8d1a29753e3a6dcf17d2903bc5 100644
--- a/authority/server/run.js
+++ b/primary/app.js
@@ -1,27 +1,27 @@
 const        path = require('path'),
-              url = require('url'),
-            wsapi = require('./wsapi.js'),
-        httputils = require('./httputils.js'),
-          connect = require('connect'),
-        webfinger = require('./webfinger.js'),
-         sessions = require('cookie-sessions'),
-          secrets = require('./secrets.js');
+fs = require('fs'),
+url = require('url'),
+wsapi = require('./lib/wsapi.js'),
+httputils = require('./lib/httputils.js'),
+webfinger = require('./lib/webfinger.js'),
+sessions = require('cookie-sessions'),
+secrets = require('./lib/secrets.js');
 
-const STATIC_DIR = path.join(path.dirname(__dirname), "static");
+// create the var directory if it doesn't exist
+var VAR_DIR = path.join(__dirname, "var");
+try { fs.mkdirSync(VAR_DIR, 0755); } catch(e) { }
 
-const COOKIE_SECRET = secrets.hydrateSecret('cookie_secret', __dirname);
+const STATIC_DIR = path.join(__dirname, "static");
 
-exports.handler = function(request, response, next) {
+const COOKIE_SECRET = secrets.hydrateSecret('cookie_secret', VAR_DIR);
+
+function handler(request, response, next) {
     // dispatch!
     var urlpath = url.parse(request.url).pathname;
 
     if (urlpath === '/sign_in') {
-        // a little remapping!
         request.url = "/dialog/index.html";
         next();
-    } else if (urlpath === '/register_iframe') {
-        request.url = "/dialog/register_iframe.html";
-        next();
     } else if (/^\/wsapi\/\w+$/.test(urlpath)) {
         try {
             var method = path.basename(urlpath);
@@ -44,16 +44,17 @@ exports.handler = function(request, response, next) {
     } else if (urlpath === "/code_update") {
         console.log("code updated.  shutting down.");
         process.exit();
-    } else {
-        next();
     }
 };
 
-exports.setup = function(server) {
+exports.varDir = VAR_DIR;
+
+exports.setup = function(app) {
     var week = (7 * 24 * 60 * 60 * 1000);
-    server.use(sessions({
+    app.use(sessions({
         secret: COOKIE_SECRET,
-        session_key: "browserid_state",
+        session_key: "primary_state",
         path: '/'
     }));
-}
+    app.use(handler);
+};
diff --git a/primary/server/db.js b/primary/lib/db.js
similarity index 97%
rename from primary/server/db.js
rename to primary/lib/db.js
index ff44730468f91055182d544d8614cb5e54579ec2..e30356da5db9df584a0ee27061764ee12d78811b 100644
--- a/primary/server/db.js
+++ b/primary/lib/db.js
@@ -3,7 +3,7 @@ const sqlite = require('sqlite'),
 
 var db = new sqlite.Database();
 
-db.open(path.join(path.dirname(__dirname), "userdb.sqlite"), function (error) {
+db.open(path.join(path.dirname(__dirname), "var", "userdb.sqlite"), function (error) {
   if (error) {
     console.log("Couldn't open database: " + error);
     throw error;
diff --git a/primary/server/httputils.js b/primary/lib/httputils.js
similarity index 100%
rename from primary/server/httputils.js
rename to primary/lib/httputils.js
diff --git a/primary/server/secrets.js b/primary/lib/secrets.js
similarity index 100%
rename from primary/server/secrets.js
rename to primary/lib/secrets.js
diff --git a/primary/server/webfinger.js b/primary/lib/webfinger.js
similarity index 100%
rename from primary/server/webfinger.js
rename to primary/lib/webfinger.js
diff --git a/primary/server/webfinger_template.xml b/primary/lib/webfinger_template.xml
similarity index 100%
rename from primary/server/webfinger_template.xml
rename to primary/lib/webfinger_template.xml
diff --git a/primary/server/wsapi.js b/primary/lib/wsapi.js
similarity index 100%
rename from primary/server/wsapi.js
rename to primary/lib/wsapi.js
diff --git a/primary/run.js b/primary/run.js
new file mode 100755
index 0000000000000000000000000000000000000000..228d290925afbb7cc614b02ad39143dd0bb4660c
--- /dev/null
+++ b/primary/run.js
@@ -0,0 +1,25 @@
+#!/usr/bin/env node
+
+var   sys = require("sys"),
+     path = require("path"),
+       fs = require("fs"),
+  express = require("express");
+
+var PRIMARY_HOST = "127.0.0.1";
+var PRIMARY_PORT = 62900;
+
+var handler = require("./app.js");
+
+var app = express.createServer();
+
+app.use(express.logger({
+    stream: fs.createWriteStream(path.join(handler.varDir, "server.log"))
+}));
+
+// let the specific server interact directly with the connect server to register their middleware
+if (handler.setup) handler.setup(app);
+
+// use the connect 'static' middleware for serving of static files (cache headers, HTTP range, etc)
+app.use(express.static(path.join(__dirname, "static")));
+
+app.listen(PRIMARY_PORT, PRIMARY_HOST);
diff --git a/primary/server/run.js b/primary/server/run.js
deleted file mode 100644
index 02e579bcb6eff3a49cf6c35a95d806dd22fe84e5..0000000000000000000000000000000000000000
--- a/primary/server/run.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const        path = require('path'),
-              url = require('url'),
-            wsapi = require('./wsapi.js'),
-        httputils = require('./httputils.js'),
-          connect = require('connect'),
-        webfinger = require('./webfinger.js'),
-         sessions = require('cookie-sessions'),
-          secrets = require('./secrets.js');
-
-const STATIC_DIR = path.join(path.dirname(__dirname), "static");
-
-const COOKIE_SECRET = secrets.hydrateSecret('cookie_secret', __dirname);
-
-exports.handler = function(request, response, serveFile) {
-  // dispatch!
-  var urlpath = url.parse(request.url).pathname;
-
-  if (urlpath === '/sign_in') {
-    serveFile(path.join(STATIC_DIR, "dialog", "index.html"), response);
-  } else if (/^\/wsapi\/\w+$/.test(urlpath)) {
-    try {
-      var method = path.basename(urlpath);
-      wsapi[method](request, response);
-    } catch(e) {
-      var errMsg = "oops, error executing wsapi method: " + method + " (" + e.toString() +")";
-      console.log(errMsg);
-      httputils.fourOhFour(response, errMsg);
-    }
-  } else if (/^\/users\/[^\/]+.xml$/.test(urlpath)) {
-    var identity = path.basename(urlpath).replace(/.xml$/, '').replace(/^acct:/, '');
-
-    webfinger.renderUserPage(identity, function (resultDocument) {
-      if (resultDocument === undefined) {
-        httputils.fourOhFour(response, "I don't know anything about: " + identity + "\n");
-      } else {
-        httputils.xmlResponse(response, resultDocument);
-      }
-    });
-  } else if (urlpath === "/code_update") {
-      console.log("code updated.  shutting down.");
-      process.exit();
-  } else {
-    // node.js takes care of sanitizing the request path
-    if (urlpath == "/") urlpath = "/index.html"
-    serveFile(path.join(STATIC_DIR, urlpath), response);
-  }
-};
-
-exports.setup = function(server) {
-  var week = (7 * 24 * 60 * 60 * 1000);
-  server.use(sessions({
-      secret: COOKIE_SECRET,
-      session_key: "primary_state",
-      path: '/'
-  }));
-}
diff --git a/primary/server/standalone.js b/primary/server/standalone.js
deleted file mode 100644
index 8caedb8543bd13bf00b45beeec251632ff4c83b7..0000000000000000000000000000000000000000
--- a/primary/server/standalone.js
+++ /dev/null
@@ -1,68 +0,0 @@
-var   sys = require("sys"),
-     http = require("http"),
-      url = require("url"),
-     path = require("path"),
-       fs = require("fs"),
-  connect = require("connect");
-
-var PRIMARY_HOST = "127.0.0.1";
-var PRIMARY_PORT = 62900;
-
-var handler = require("./run.js");
-
-function subHostNames(data) {
-    return data;
-}
-
-function serveFile(filename, response) {
-  path.exists(filename, function(exists) {
-    if(!exists) {
-      response.writeHead(404, {"Content-Type": "text/plain"});
-      response.write("404 Not Found");
-      response.end();
-      return;
-    }
-
-    fs.readFile(filename, "binary", function(err, data) {
-      if(err) {
-        response.writeHead(500, {"Content-Type": "text/plain"});
-        response.write(err + "\n");
-        response.end();
-        return;
-      }
-
-      var exts = {
-        ".js":   "text/javascript",
-        ".css":  "text/css",
-        ".html": "text/html",
-        ".webapp": "application/x-web-app-manifest+json",
-        ".png": "image/png",
-        ".ico": "image/x-icon"
-      };
-
-      var ext = path.extname(filename);
-      var mimeType = exts[ext] || "application/octet-stream";
-
-      data = subHostNames(data);
-
-      response.writeHead(200, {"Content-Type": mimeType});
-      response.write(data, "binary");
-      response.end();
-    });
-  });
-}
-
-var server = connect.createServer().use(connect.favicon())
-    .use(connect.logger({
-        format: ":status :method :remote-addr :response-time :url",
-        stream: fs.createWriteStream(path.join(__dirname, "server.log"))
-    }));
-
-// let the specific server interact directly with the connect server to register their middleware 
-if (handler.setup) handler.setup(server);
-
-server.use(function(req, resp, next) {
-    handler.handler(req, resp, serveFile, subHostNames);
-});
-
-server.listen(PRIMARY_PORT, PRIMARY_HOST);
diff --git a/rp/index.html b/rp/index.html
index c331e8d58d716dd651378ab10807d5f467bf5129..167669a35868e549e25545edcda5f1557bec8371 100644
--- a/rp/index.html
+++ b/rp/index.html
@@ -69,7 +69,7 @@ a:hover { border-bottom: 2px solid black ; }
 <div class="step">
   <div class="number">5.</div>
   <div class="desc"><b>Verification Response</b>: The verification server responds to the site to tell it whether or not the verification blob is valid:</div>
-  <div class="output" id="oVerificationResponse">...waiting for party commencement...</div>
+  <div class="output" id="oVerificationResponse"><pre>...waiting for party commencement...</pre></div>
 </div>
 
 <div class="step">
@@ -81,19 +81,6 @@ a:hover { border-bottom: 2px solid black ; }
 <script src="jquery-min.js"></script>
 <script src="https://browserid.org/include.js"></script>
 <script>
-  function dumpObject(obj) {
-    var htmlRep = "";
-    for (var k in obj) {
-      if (obj.hasOwnProperty(k) && typeof obj[k] === 'string') {
-        htmlRep += "<b>" + k + ":</b> " + obj[k] + "<br/>";
-      } else if (k === 'valid-until') {
-        htmlRep += "<b>" + k + ":</b> " + (new Date(obj[k])).toString() + "<br/>";
-      }
-
-    }
-    return htmlRep;
-  }
-
   $(document).ready(function() {
     $("#partyStarter").click(function() {
       navigator.id.getVerifiedEmail(function(assertion) {
@@ -101,21 +88,22 @@ a:hover { border-bottom: 2px solid black ; }
           alert("couldn't get the users email address!"); 
         } else {
           // Now we'll send this assertion over to the verification server for validation
-          $("#oAssertion").empty().html(dumpObject(assertion));
+          $("#oAssertion").empty().text(assertion);
 
-          var url = "http://browserid.org/verify?assertion=" + window.encodeURIComponent(assertion) +
+          var url = "https://browserid.org/verify?assertion=" + window.encodeURIComponent(assertion) +
                     "&audience=" + window.encodeURIComponent(window.location.host);
           $("#oVerificationRequest").empty().text(url);
 
           $.ajax({
             url: url,
+            dataType: "json",
             success: function(data, textStatus, jqXHR) {
-              $("#oVerificationResponse").empty().text(JSON.stringify(data, null, 4));
+              $("#oVerificationResponse > pre").empty().text(JSON.stringify(data, null, 4));
             },
             error: function(jqXHR, textStatus, errorThrown) {
-              $("#oVerificationResponse").empty().text(jqXHR.responseText);
+              $("#oVerificationResponse > pre").empty().text(JSON.stringify(JSON.parse(jqXHR.responseText), null, 4));
             }
-          })
+          });
         }
       });
     });
diff --git a/run.js b/run.js
old mode 100644
new mode 100755
index dbb48883df68978f69d560dc3386d86600033235..915da6e668ea5f275f8db68c9d14f65ecf1a9702
--- a/run.js
+++ b/run.js
@@ -1,3 +1,5 @@
+#!/usr/bin/env node
+
 // a little node webserver designed to run the unit tests herein
 
 var   sys = require("sys"),
@@ -5,13 +7,14 @@ var   sys = require("sys"),
       url = require("url"),
      path = require("path"),
        fs = require("fs"),
-  connect = require("connect");
+  express = require("express");
 
 var PRIMARY_HOST = "127.0.0.1";
 
-// all bound webservers stored in this lil' object
 var boundServers = [ ];
 
+// given a buffer, find and replace all production hostnames
+// with development URLs
 function subHostNames(data) {
   for (var i = 0; i < boundServers.length; i++) {
     var o = boundServers[i]
@@ -41,74 +44,77 @@ function subHostNames(data) {
   return data;
 }
 
-function createServer(obj) {
-    var server = connect.createServer().use(connect.favicon()).use(connect.logger());
-
-    // this file is a *test* harness, to make it go, we'll insert a little handler that
-    // substitutes output, changing production URLs to developement URLs.
-    server.use(function(req, resp, next) {
-        // cache the *real* functions
-        var realWrite = resp.write;
-        var realEnd = resp.end;
-        var realWriteHead = resp.writeHead;
-
-        var buf = undefined;
-        var enc = undefined;
-        var contentType = undefined;
-
-        resp.writeHead = function (sc, reason, hdrs) {
-            var h = undefined;
-            if (typeof hdrs === 'object') h = hdrs;
-            else if (typeof reason === 'object') h = reason; 
-            for (var k in h) {
-                if (k.toLowerCase() === 'content-type') {
-                    contentType = h[k];
-                    break;
-                }
+// Middleware that intercepts outbound textual responses and substitutes
+// in development hostnames
+function substitutionMiddleware(req, resp, next) {
+    // cache the *real* functions
+    var realWrite = resp.write;
+    var realEnd = resp.end;
+    var realWriteHead = resp.writeHead;
+
+    var buf = undefined;
+    var enc = undefined;
+    var contentType = undefined;
+
+    resp.writeHead = function (sc, reason, hdrs) {
+        var h = undefined;
+        if (typeof hdrs === 'object') h = hdrs;
+        else if (typeof reason === 'object') h = reason; 
+        for (var k in h) {
+            if (k.toLowerCase() === 'content-type') {
+                contentType = h[k];
+                break;
             }
-            if (!contentType) contentType = resp.getHeader('content-type');
-            if (!contentType) contentType = "application/unknown";
-            realWriteHead(sc, reason, hdrs);
-        };
-
-        resp.write = function (chunk, encoding) {
-            if (buf) buf += chunk;
-            else buf = chunk;
-            enc = encoding;
-        };
-
-        resp.end = function() {
-            if (!contentType) contentType = resp.getHeader('content-type');
-            if (contentType && (contentType === "application/javascript" ||
-                                contentType.substr(0,4) === 'text'))
-            {
-                if (buf) {
-                    var l = buf.length;
-                    buf = subHostNames(buf);
-                    if (l != buf.length) resp.setHeader('Content-Length', buf.length);
-                }
+        }
+        if (!contentType) contentType = resp.getHeader('content-type');
+        if (!contentType) contentType = "application/unknown";
+        realWriteHead(sc, reason, hdrs);
+    };
+
+    resp.write = function (chunk, encoding) {
+        if (buf) buf += chunk;
+        else buf = chunk;
+        enc = encoding;
+    };
+
+    resp.end = function() {
+        if (!contentType) contentType = resp.getHeader('content-type');
+        if (contentType && (contentType === "application/javascript" ||
+                            contentType.substr(0,4) === 'text'))
+        {
+            if (buf) {
+                var l = buf.length;
+                buf = subHostNames(buf);
+                if (l != buf.length) resp.setHeader('Content-Length', buf.length);
             }
-            if (buf && buf.length) realWrite.call(resp, buf, enc);
-            realEnd.call(resp);
         }
+        if (buf && buf.length) realWrite.call(resp, buf, enc);
+        realEnd.call(resp);
+    }
+
+    next();
+};
 
-        next();
-    });
+function createServer(obj) {
+    var app = express.createServer();
+    app.use(express.logger());
 
-    // let the specific server interact directly with the connect server to register their middleware 
-    if (obj.setup) obj.setup(server);
+    // this file is a *test* harness, to make it go, we'll insert a little handler that
+    // substitutes output, changing production URLs to developement URLs.
+    app.use(substitutionMiddleware);
 
-    // if this site has a handler, we'll run that, otherwise serve statically
-    if (obj.handler) server.use(obj.handler);
+    // let the specific server interact directly with the express server to register their middleware,
+    // routes, etc...
+    if (obj.setup) obj.setup(app);
 
     // now set up the static resource servin'
     var p = obj.path, ps = path.join(p, "static");
     try { if (fs.statSync(ps).isDirectory()) p = ps; } catch(e) { }
-    server.use(connect.static(p));
+    app.use(express.static(p));
 
     // and listen!
-    server.listen(obj.port, PRIMARY_HOST);
-    return server;
+    app.listen(obj.port, PRIMARY_HOST);
+    return app;
 };
 
 // start up webservers on ephemeral ports for each subdirectory here.
@@ -134,7 +140,7 @@ var dirs = [
     // BrowserID: the secondary + ip + more.
     {
         name: "https://browserid.org",
-        path: path.join(__dirname, "authority")
+        path: path.join(__dirname, "browserid")
     }
 ];
 
@@ -151,7 +157,7 @@ console.log("Running test servers:");
 dirs.forEach(function(dirObj) {
   if (!fs.statSync(dirObj.path).isDirectory()) return;
   // does this server have a js handler for custom request handling?
-  var handlerPath = path.join(dirObj.path, "server", "run.js");
+  var handlerPath = path.join(dirObj.path, "app.js");
   var runJS = {};
   try {
     var runJSExists = false;
diff --git a/verifier/.gitignore b/verifier/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c4c53c804943031be0c755915d85c53e73beca7b
--- /dev/null
+++ b/verifier/.gitignore
@@ -0,0 +1 @@
+/var
diff --git a/verifier/app.js b/verifier/app.js
new file mode 100644
index 0000000000000000000000000000000000000000..7401289434840f6c03716f72b8abb911a281de34
--- /dev/null
+++ b/verifier/app.js
@@ -0,0 +1,75 @@
+const   path = require('path'),
+         url = require('url'),
+          fs = require('fs'),
+   httputils = require('./lib/httputils.js'),
+ idassertion = require('./lib/idassertion.js'),
+         jwt = require('./lib/jwt.js');
+
+// create the var directory if it doesn't exist
+var VAR_DIR = path.join(__dirname, "var");
+try { fs.mkdirSync(VAR_DIR, 0755); } catch(e) { }
+
+function handler(req, resp, next) {
+    // dispatch!
+    var parsed = url.parse(req.url, true);
+
+    var urlpath = parsed.pathname;
+
+    var assertion = parsed.query['assertion'];
+    var audience = parsed.query['audience'];
+
+    // allow client side XHR to access this WSAPI, see
+    // https://developer.mozilla.org/en/http_access_control
+    // for details
+    resp.setHeader('Access-Control-Allow-Origin', '*');
+    if (req.method === 'OPTIONS') {
+        resp.setHeader('Access-Control-Allow-Methods', 'GET');
+        resp.writeHead(200);
+        resp.end();
+        return;
+    }
+    
+    try {
+        var assertionObj = new idassertion.IDAssertion(assertion);
+        assertionObj.verify(
+            audience,
+            function(payload) {
+                result = {
+                    status : "okay",
+                    email : payload.email,
+                    audience : payload.audience,
+                    "valid-until" : payload["valid-until"],
+                    issuer : payload.issuer
+                };
+                httputils.jsonResponse(resp, result);
+            },
+            function(errorObj) {
+                httputils.jsonResponse(resp, {status:"failure", reason:errorObj});
+            }
+        );
+    } catch (e) {
+        console.log(e.stack);
+        httputils.jsonResponse(resp, {status:"failure", reason:e.toString()});
+    }
+};
+
+exports.varDir = VAR_DIR;
+
+exports.setup = function(app) {
+    // code_update is an internal api that causes the node server to
+    // shut down.  This should never be externally accessible and
+    // is used during the dead simple deployment procedure.
+    app.get("/code_update", function (req, resp) {
+        console.log("code updated.  shutting down.");
+        process.exit();
+    });
+
+    // A simple ping hook for monitoring.
+    app.get("/ping.txt", function(req ,resp) {
+        resp.writeHead(200, {"Content-Type": "text/plain"})
+        resp.write("k.");
+        resp.end();
+    });
+
+    app.use(handler);
+};
diff --git a/verifier/server/httputils.js b/verifier/lib/httputils.js
similarity index 100%
rename from verifier/server/httputils.js
rename to verifier/lib/httputils.js
diff --git a/verifier/server/idassertion.js b/verifier/lib/idassertion.js
similarity index 100%
rename from verifier/server/idassertion.js
rename to verifier/lib/idassertion.js
diff --git a/verifier/server/jwt.js b/verifier/lib/jwt.js
similarity index 100%
rename from verifier/server/jwt.js
rename to verifier/lib/jwt.js
diff --git a/verifier/server/make_assertion.js b/verifier/lib/make_assertion.js
similarity index 100%
rename from verifier/server/make_assertion.js
rename to verifier/lib/make_assertion.js
diff --git a/verifier/server/rsa.js b/verifier/lib/rsa.js
similarity index 100%
rename from verifier/server/rsa.js
rename to verifier/lib/rsa.js
diff --git a/verifier/run.js b/verifier/run.js
new file mode 100755
index 0000000000000000000000000000000000000000..34adb7f8e4bbd9b2765a0ed395c545166a883b30
--- /dev/null
+++ b/verifier/run.js
@@ -0,0 +1,20 @@
+#!/usr/bin/env node
+
+var   sys = require("sys"),
+     path = require("path"),
+       fs = require("fs"),
+  express = require("express");
+
+var PRIMARY_HOST = "127.0.0.1";
+var PRIMARY_PORT = 62800;
+
+var handler = require("./app.js");
+
+var app = express.createServer().use(express.logger({
+    stream: fs.createWriteStream(path.join(handler.varDir, "server.log"))
+}));
+
+// let the specific server interact directly with the express server to register their middleware
+if (handler.setup) handler.setup(app);
+
+app.listen(PRIMARY_PORT, PRIMARY_HOST);
diff --git a/verifier/server/run.js b/verifier/server/run.js
deleted file mode 100644
index cc9e31fb8234ecfe307ddea152d8345573ef8f31..0000000000000000000000000000000000000000
--- a/verifier/server/run.js
+++ /dev/null
@@ -1,70 +0,0 @@
-const   path = require('path'),
-         url = require('url'),
-   httputils = require('./httputils.js'),
- idassertion = require('./idassertion.js'),
-         jwt = require('./jwt.js');
-
-
-exports.handler = function(req, resp, serveFile) {
-    // dispatch!
-    var parsed = url.parse(req.url, true);
-
-    var urlpath = parsed.pathname;
-
-    // code_update is an internal api that causes the node server to
-    // shut down.  This should never be externally accessible and
-    // is used during the dead simple deployment.
-    //
-    // NOTE: this should be reworked as the project gets more serious.
-    if (urlpath === "/code_update") {
-        console.log("code updated.  shutting down.");
-        process.exit();
-    }
-
-    // A simple ping hook for monitoring.
-    else if (urlpath === "/ping.txt") {
-        resp.writeHead(200, {"Content-Type": "text/plain"})
-        resp.write("k.");
-        resp.end();
-    }
-
-    // the verification API, the main reason this server exists!
-    else {
-        var assertion = parsed.query['assertion'];
-        var audience = parsed.query['audience'];
-
-        // allow client side XHR to access this WSAPI, see
-        // https://developer.mozilla.org/en/http_access_control
-        // for details
-        resp.setHeader('Access-Control-Allow-Origin', '*');
-        if (req.method === 'OPTIONS') {
-            resp.setHeader('Access-Control-Allow-Methods', 'GET');
-            resp.writeHead(200);
-            resp.end();
-            return;
-        }
-
-        try {
-            var assertionObj = new idassertion.IDAssertion(assertion);
-            assertionObj.verify(
-                audience,
-                function(payload) {
-                    result = {
-                        status : "okay",
-                        email : payload.email,
-                        audience : payload.audience,
-                        "valid-until" : payload["valid-until"],
-                        issuer : payload.issuer
-                    };
-                    httputils.jsonResponse(resp, result);
-                },
-                function(errorObj) {
-                    httputils.jsonResponse(resp, {status:"failure", reason:errorObj});
-                }
-            );
-        } catch (e) {
-            console.log(e.stack);
-            httputils.jsonResponse(resp, {status:"failure", reason:e.toString()});
-        }
-    }
-};
diff --git a/verifier/server/standalone.js b/verifier/server/standalone.js
deleted file mode 100644
index 3e018b438547b6915dea820339893be4cad3d29f..0000000000000000000000000000000000000000
--- a/verifier/server/standalone.js
+++ /dev/null
@@ -1,67 +0,0 @@
-var   sys = require("sys"),
-     http = require("http"),
-      url = require("url"),
-     path = require("path"),
-       fs = require("fs"),
-  connect = require("connect");
-
-var PRIMARY_HOST = "127.0.0.1";
-var PRIMARY_PORT = 62800;
-
-var handler = require("./run.js");
-
-function subHostNames(data) {
-    return data;
-}
-
-function serveFile(filename, response) {
-  path.exists(filename, function(exists) {
-    if(!exists) {
-      response.writeHead(404, {"Content-Type": "text/plain"});
-      response.write("404 Not Found");
-      response.end();
-      return;
-    }
-
-    fs.readFile(filename, "binary", function(err, data) {
-      if(err) {
-        response.writeHead(500, {"Content-Type": "text/plain"});
-        response.write(err + "\n");
-        response.end();
-        return;
-      }
-
-      var exts = {
-        ".js":   "text/javascript",
-        ".css":  "text/css",
-        ".html": "text/html",
-        ".webapp": "application/x-web-app-manifest+json",
-        ".png": "image/png",
-        ".ico": "image/x-icon"
-      };
-
-      var ext = path.extname(filename);
-      var mimeType = exts[ext] || "application/octet-stream";
-
-      data = subHostNames(data);
-
-      response.writeHead(200, {"Content-Type": mimeType});
-      response.write(data, "binary");
-      response.end();
-    });
-  });
-}
-
-var server = connect.createServer().use(connect.favicon())
-    .use(connect.logger({
-        stream: fs.createWriteStream(path.join(__dirname, "server.log"))
-    }));
-
-// let the specific server interact directly with the connect server to register their middleware 
-if (handler.setup) handler.setup(server);
-
-server.use(function(req, resp, next) {
-    handler.handler(req, resp, serveFile, subHostNames);
-});
-
-server.listen(PRIMARY_PORT, PRIMARY_HOST);
diff --git a/verifier/server/test_verification.js b/verifier/tests/run.js
similarity index 98%
rename from verifier/server/test_verification.js
rename to verifier/tests/run.js
index 58a274c5d1019f3ace18bc6654dcbcc145228cd5..c7c457c5c52b87f9340ac049ddd54b035a053ade 100755
--- a/verifier/server/test_verification.js
+++ b/verifier/tests/run.js
@@ -1,6 +1,7 @@
 #!/usr/local/bin/node
-const jwt = require("./jwt.js");
-const idassertion = require("./idassertion.js");
+
+const jwt = require("../lib/jwt.js");
+const idassertion = require("../lib/idassertion.js");
 const vows = require('vows');
 const assert = require('assert');
 
@@ -45,7 +46,6 @@ vows.describe('Assertion creation').addBatch({
 
 
 vows.describe('Assertion validation').addBatch({
-
     'Assertion validation with a valid assertion': {
         topic: 
           function() {
@@ -67,4 +67,4 @@ vows.describe('Assertion validation').addBatch({
     }
 }).run(); // Run it
 
-  
\ No newline at end of file
+