diff --git a/browserid/app.js b/browserid/app.js
index 96e9dad9dc39d47c10007498746db6af66936295..5ad05ab01c5673a86326129164decae351f421f2 100644
--- a/browserid/app.js
+++ b/browserid/app.js
@@ -183,7 +183,7 @@ exports.setup = function(server) {
       httpOnly: true,
       // IMPORTANT: we allow users to go 1 weeks on the same device
       // without entering their password again
-      maxAge: (7 * 24 * 60 * 60 * 1000),
+      maxAge: configuration.get('authentication_duration_ms'),
       secure: overSSL
     }
   });
diff --git a/browserid/lib/db_json.js b/browserid/lib/db_json.js
index 2a2c21859055b1cc08215aa42030e6a3ba6c23cf..42c6ecc49d24d7868ed2731a55e93d02373ad31a 100644
--- a/browserid/lib/db_json.js
+++ b/browserid/lib/db_json.js
@@ -75,11 +75,6 @@ function flush() {
   }
 }
 
-// when should a key created right now expire?
-function getExpiryTime() {
-  return ((new Date()).getTime() + (14 * 24 * 60 * 60 * 1000));
-}
-
 // when unit_test is set in configuration, database should be
 // ephemeral.  which simply means we use a temp file and delete
 // on close;
diff --git a/browserid/lib/wsapi.js b/browserid/lib/wsapi.js
index 93c570f1b991a9fbc930c55bc370cc8e3bef0e1e..602b30fa9e022598f9bb9c5d3f2e7998ed8a1f04 100644
--- a/browserid/lib/wsapi.js
+++ b/browserid/lib/wsapi.js
@@ -47,7 +47,7 @@ bcrypt = require('bcrypt'),
 crypto = require('crypto'),
 logger = require('../../libs/logging.js').logger,
 ca = require('./ca.js'),
-BCRYPT_WORK_FACTOR = 12;
+configuration = require('../../libs/configuration.js');
 
 function checkParams(params) {
   return function(req, resp, next) {
@@ -72,9 +72,37 @@ function checkParams(params) {
   };
 }
 
+// log a user out, clearing everything from their session except the csrf token
+function clearAuthenticatedUser(session) {
+  Object.keys(session).forEach(function(k) {
+    if (k !== 'csrf') delete session[k];
+  });
+}
+
+
+function setAuthenticatedUser(session, email) {
+  session.authenticatedUser = email;
+  session.authenticatedAt = new Date();
+}
+
 function isAuthed(req) {
-  var result= (req.session && typeof req.session.authenticatedUser === 'string');
-  return result;
+  var who;
+  try {
+    if (req.session.authenticatedUser) {
+      if (!Date.parse(req.session.authenticatedAt) > 0) throw "bad timestamp";
+      if (new Date() - new Date(req.session.authenticatedAt) >
+          configuration.get('authentication_duration_ms'))
+      {
+        throw "expired";
+      }
+      who = req.session.authenticatedUser;
+    }
+  } catch(e) {
+    logger.debug("Session authentication has expired:", e);
+    clearAuthenticatedUser(req.session);
+  }
+
+  return who;
 }
 
 // turned this into a proper middleware
@@ -87,13 +115,6 @@ function checkAuthed(req, resp, next) {
 }
 
 function setup(app) {
-  // log a user out, clearing everything from their session except the csrf token
-  function clearAuthenticatedUser(session) {
-    Object.keys(session).forEach(function(k) {
-      if (k !== 'csrf') delete session[k];
-    });
-  }
-
   // return the CSRF token
   // IMPORTANT: this should be safe because it's only readable by same-origin code
   // but we must be careful that this is never a JSON structure that could be hijacked
@@ -142,7 +163,7 @@ function setup(app) {
     }
 
     // bcrypt the password
-    bcrypt.gen_salt(BCRYPT_WORK_FACTOR, function (err, salt) {
+    bcrypt.gen_salt(configuration.get('bcrypt_work_factor'), function (err, salt) {
       if (err) {
         winston.error("error generating salt with bcrypt: " + err);
         return resp.json(false);
@@ -222,7 +243,7 @@ function setup(app) {
       db.checkAuth(v.email, function(hash) {
         if (hash === v.hash) {
           delete req.session.pendingVerification;
-          req.session.authenticatedUser = v.email;
+          setAuthenticatedUser(req.session, v.email);
           resp.json('complete');
         } else {
           resp.json('pending');
@@ -247,7 +268,7 @@ function setup(app) {
         }
         if (success) {
           if (!req.session) req.session = {};
-          req.session.authenticatedUser = req.body.email;
+          setAuthenticatedUser(req.session, req.body.email);
 
           // if the work factor has changed, update the hash here
           
@@ -309,7 +330,7 @@ function setup(app) {
       // same account, we certify the key
       // we certify it for a day for now
       var expiration = new Date();
-      expiration.setTime(new Date().valueOf() + (24*3600*1000));
+      expiration.setTime(new Date().valueOf() + configuration.get('certificate_validity_ms'));
       var cert = ca.certify(req.body.email, pk, expiration);
       
       resp.writeHead(200, {'Content-Type': 'text/plain'});
diff --git a/libs/configuration.js b/libs/configuration.js
index 1f6b993850eb1b967a61e8438bb87de97c72def6..b8b34ff53efdbe6bb072cdcd9500205e32db6b18 100644
--- a/libs/configuration.js
+++ b/libs/configuration.js
@@ -70,7 +70,10 @@ g_configs.production = {
   database: {
     driver: "mysql",
     user: 'browserid'
-  }
+  },
+  bcrypt_work_factor: 12,
+  authentication_duration_ms: (7 * 24 * 60 * 60 * 1000),
+  certificate_validity_ms: (24 * 60 * 60 * 1000)
 };
 
 // beta (diresworb.org) the only difference from production 
@@ -91,7 +94,10 @@ g_configs.local =  {
   email_to_console: true, // don't send email, just dump verification URLs to console.
   use_minified_resources: false,
   var_path: path.join(__dirname, "..", "var"),
-  database: { driver: "json" }
+  database: { driver: "json" },
+  bcrypt_work_factor: g_configs.production.bcrypt_work_factor,
+  authentication_duration_ms: g_configs.production.authentication_duration_ms,
+  certificate_validity_ms: g_configs.production.certificate_validity_ms
 };
 
 // test environments are variations on local