diff --git a/Happstack/Authenticate/Controller.hs b/Happstack/Authenticate/Controller.hs
--- a/Happstack/Authenticate/Controller.hs
+++ b/Happstack/Authenticate/Controller.hs
@@ -52,6 +52,31 @@
 
     }]);
 
+    happstackAuthentication.factory('authInterceptor', ['$rootScope', '$q', '$window', 'userService', function ($rootScope, $q, $window, userService) {
+      return {
+        'request': function (config) {
+          config.headers = config.headers || {};
+          u = userService.getUser();
+          if (u && u.token) {
+            config.headers.Authorization = 'Bearer ' + u.token;
+          }
+          return config;
+        },
+        'responseError': function (rejection) {
+          if (rejection.status === 401) {
+            // handle the case where the user is not authenticated
+            userService.clearUser();
+            document.cookie = 'atc=; path=/; expires=Thu, 01-Jan-70 00:00:01 GMT;';
+          }
+          return $q.reject(rejection);
+        }
+      };
+    }]);
+
+    happstackAuthentication.config(['$httpProvider', function ($httpProvider) {
+      $httpProvider.interceptors.push('authInterceptor');
+    }]);
+
     // add userService
     happstackAuthentication.factory('userService', ['$rootScope', function ($rootScope) {
 
@@ -87,6 +112,18 @@
         },
 
         getUser: function() {
+          function getCookie(cname) {
+            var name = cname + "=";
+            var ca = document.cookie.split(';');
+            for(var i=0; i<ca.length; i++) {
+              var c = ca[i];
+              while (c.charAt(0)==' ') c = c.substring(1);
+              if (c.indexOf(name) == 0) return c.substring(name.length,c.length);
+            }
+            return "";
+          };
+          if (getCookie('atc').length == 0 && this.userCache.isAuthenticated == true)
+            this.clearUser();
           return(this.userCache);
         },
 
diff --git a/Happstack/Authenticate/Core.hs b/Happstack/Authenticate/Core.hs
--- a/Happstack/Authenticate/Core.hs
+++ b/Happstack/Authenticate/Core.hs
@@ -33,7 +33,11 @@
 -}
 
 module Happstack.Authenticate.Core
-    ( HappstackAuthenticateI18N(..)
+    ( AuthenticateConfig(..)
+    , isAuthAdmin
+    , usernameAcceptable
+    , requireEmail
+    , HappstackAuthenticateI18N(..)
     , UserId(..)
     , unUserId
     , rUserId
@@ -45,6 +49,7 @@
     , Username(..)
     , unUsername
     , rUsername
+    , usernamePolicy
     , Email(..)
     , unEmail
     , User(..)
@@ -140,28 +145,82 @@
 import qualified Data.Text             as Text
 import qualified Data.Text.Encoding    as Text
 import Data.Time                       (UTCTime, addUTCTime, diffUTCTime, getCurrentTime)
+import Data.Time.Clock.POSIX           (utcTimeToPOSIXSeconds, posixSecondsToUTCTime)
 import Data.UserId                     (UserId(..), rUserId, succUserId, unUserId)
 import GHC.Generics                    (Generic)
 import Happstack.Server                (Cookie(secure), CookieLife(Session, MaxAge), Happstack, ServerPartT, Request(rqSecure), Response, addCookie, askRq, expireCookie, getHeaderM, lookCookie, lookCookieValue, mkCookie, notFound, toResponseBS)
+-- import Happstack.Server.Internal.Clock (getApproximateUTCTime)
 import Language.Javascript.JMacro
-import Prelude                         hiding ((.), id)
+import Prelude                         hiding ((.), id, exp)
 import System.IO                       (IOMode(ReadMode), withFile)
 import System.Random                   (randomRIO)
 import Text.Boomerang.TH               (makeBoomerangs)
 import Text.Shakespeare.I18N           (RenderMessage(renderMessage), mkMessageFor)
-import Web.JWT                         (Algorithm(HS256), JWT, VerifiedJWT, JWTClaimsSet(..), encodeSigned, claims, decode, decodeAndVerifySignature, secret, verify)
+import Web.JWT                         (Algorithm(HS256), JWT, VerifiedJWT, JWTClaimsSet(..), encodeSigned, claims, decode, decodeAndVerifySignature, secondsSinceEpoch, intDate, secret, verify)
 import Web.Routes                      (RouteT, PathInfo(..), nestURL)
 import Web.Routes.Boomerang
 import Web.Routes.Happstack            ()
 import Web.Routes.TH                   (derivePathInfo)
 
-data HappstackAuthenticateI18N = HappstackAuthenticateI18N
-
 -- | when creating JSON field names, drop the first character. Since
 -- we are using lens, the leading character should always be _.
 jsonOptions :: Options
 jsonOptions = defaultOptions { fieldLabelModifier = drop 1 }
 
+data HappstackAuthenticateI18N = HappstackAuthenticateI18N
+
+------------------------------------------------------------------------------
+-- CoreError
+------------------------------------------------------------------------------
+
+-- | the `CoreError` type is used to represent errors in a language
+-- agnostic manner. The errors are translated into human readable form
+-- via the I18N translations.
+data CoreError
+  = HandlerNotFound -- AuthenticationMethod
+  | URLDecodeFailed
+  | UsernameAlreadyExists
+  | AuthorizationRequired
+  | Forbidden
+  | JSONDecodeFailed
+  | InvalidUserId
+  | UsernameNotAcceptable
+  | InvalidEmail
+  | TextError Text
+    deriving (Eq, Ord, Read, Show, Data, Typeable, Generic)
+instance ToJSON   CoreError where toJSON    = genericToJSON    jsonOptions
+instance FromJSON CoreError where parseJSON = genericParseJSON jsonOptions
+
+instance ToJExpr CoreError where
+    toJExpr = toJExpr . toJSON
+
+deriveSafeCopy 0 'base ''CoreError
+
+mkMessageFor "HappstackAuthenticateI18N" "CoreError" "messages/core" ("en")
+
+data Status
+    = Ok
+    | NotOk
+    deriving (Eq, Ord, Read, Show, Data, Typeable, Generic)
+deriveSafeCopy 1 'base ''Status
+-- makeLenses ''Status
+makeBoomerangs ''Status
+
+instance ToJSON   Status where toJSON    = genericToJSON    jsonOptions
+instance FromJSON Status where parseJSON = genericParseJSON jsonOptions
+
+data JSONResponse = JSONResponse
+    { _jrStatus :: Status
+    , _jrData   :: A.Value
+    }
+    deriving (Eq, Read, Show, Data, Typeable, Generic)
+-- deriveSafeCopy 1 'base ''JSONResponse
+makeLenses ''JSONResponse
+makeBoomerangs ''JSONResponse
+
+instance ToJSON   JSONResponse where toJSON    = genericToJSON    jsonOptions
+instance FromJSON JSONResponse where parseJSON = genericParseJSON jsonOptions
+
 -- | convert a value to a JSON encoded 'Response'
 toJSONResponse :: (RenderMessage HappstackAuthenticateI18N e, ToJSON a) => Either e a -> Response
 toJSONResponse (Left e)  = toJSONError   e
@@ -169,11 +228,14 @@
 
 -- | convert a value to a JSON encoded 'Response'
 toJSONSuccess :: (ToJSON a) => a -> Response
-toJSONSuccess a = toResponseBS "application/json" (A.encode a)
+toJSONSuccess a = toResponseBS "application/json" (A.encode (JSONResponse Ok (A.toJSON a)))
 
 -- | convert an error to a JSON encoded 'Response'
+--
+-- FIXME: I18N
 toJSONError :: forall e. (RenderMessage HappstackAuthenticateI18N e) => e -> Response
-toJSONError e = toResponseBS "application/json" (A.encode (A.object ["error" A..= renderMessage HappstackAuthenticateI18N ["en"] e]))
+toJSONError e = toResponseBS "application/json" (A.encode (JSONResponse NotOk (A.toJSON (renderMessage HappstackAuthenticateI18N ["en"] e))))
+--                (A.encode (A.object ["error" A..= renderMessage HappstackAuthenticateI18N ["en"] e]))
 
 ------------------------------------------------------------------------------
 
@@ -261,6 +323,31 @@
              (ixFun $ maybeToList . view email)
 
 ------------------------------------------------------------------------------
+-- AuthenticateConfig
+------------------------------------------------------------------------------
+
+-- | Various configuration options that apply to all authentication methods
+data AuthenticateConfig = AuthenticateConfig
+    { _isAuthAdmin        :: UserId -> IO Bool           -- ^ can user administrate the authentication system?
+    , _usernameAcceptable :: Username -> Maybe CoreError -- ^ enforce username policies, valid email, etc. 'Nothing' == ok, 'Just Text' == error message
+    , _requireEmail       :: Bool
+    }
+    deriving (Typeable, Generic)
+makeLenses ''AuthenticateConfig
+
+-- | a very basic policy for 'userAcceptable'
+--
+-- Enforces:
+--
+--  'Username' can not be empty
+usernamePolicy :: Username
+               -> Maybe CoreError
+usernamePolicy username =
+    if Text.null $ username ^. unUsername
+    then Just UsernameNotAcceptable
+    else Nothing
+
+------------------------------------------------------------------------------
 -- SharedSecret
 ------------------------------------------------------------------------------
 
@@ -304,32 +391,6 @@
 initialSharedSecrets = Map.empty
 
 ------------------------------------------------------------------------------
--- CoreError
-------------------------------------------------------------------------------
-
--- | the `CoreError` type is used to represent errors in a language
--- agnostic manner. The errors are translated into human readable form
--- via the I18N translations.
-data CoreError
-  = HandlerNotFound -- AuthenticationMethod
-  | URLDecodeFailed
-  | UsernameAlreadyExists
-  | AuthorizationRequired
-  | Forbidden
-  | JSONDecodeFailed
-  | InvalidUserId
-    deriving (Eq, Ord, Read, Show, Data, Typeable, Generic)
-instance ToJSON   CoreError where toJSON    = genericToJSON    jsonOptions
-instance FromJSON CoreError where parseJSON = genericParseJSON jsonOptions
-
-instance ToJExpr CoreError where
-    toJExpr = toJExpr . toJSON
-
-deriveSafeCopy 0 'base ''CoreError
-
-mkMessageFor "HappstackAuthenticateI18N" "CoreError" "messages/core" ("en")
-
-------------------------------------------------------------------------------
 -- NewAccountMode
 ------------------------------------------------------------------------------
 
@@ -425,17 +486,16 @@
 createUser :: User
            -> Update AuthenticateState (Either CoreError User)
 createUser u =
-  do as@AuthenticateState{..} <- get
-     if IxSet.null $ (as ^. users) @= (u ^. username)
-       then do
-         let user' = set userId _nextUserId u
-             as' = as { _users      = IxSet.insert user' _users
-                      , _nextUserId = succ _nextUserId
-                      }
-         put as'
-         return (Right user')
-       else
-         return (Left UsernameAlreadyExists)
+    do as@AuthenticateState{..} <- get
+       if IxSet.null $ (as ^. users) @= (u ^. username)
+         then do let user' = set userId _nextUserId u
+                     as' = as { _users      = IxSet.insert user' _users
+                              , _nextUserId = succ _nextUserId
+                              }
+                 put as'
+                 return (Right user')
+         else
+             return (Left UsernameAlreadyExists)
 
 -- | Create a new 'User'. This will allocate a new 'UserId'. The
 -- returned 'User' value will have the updated 'UserId'.
@@ -452,7 +512,6 @@
      put as'
      return user
 
-
 -- | Update an existing 'User'. Must already have a valid 'UserId'.
 updateUser :: User
            -> Update AuthenticateState ()
@@ -486,6 +545,8 @@
        return $ getOne $ us @= userId
 
 -- | look up a 'User' by their 'Email'
+--
+-- NOTE: if the email is associated with more than one account this will return 'Nothing'
 getUserByEmail :: Email
                -> Query AuthenticateState (Maybe User)
 getUserByEmail email =
@@ -558,25 +619,29 @@
 -- `OpenId` realm, change the registeration mode, etc.
 issueToken :: (MonadIO m) =>
               AcidState AuthenticateState
-           -> (UserId -> IO Bool)          -- ^ isAuthAdmin function
+           -> AuthenticateConfig
            -> User                         -- ^ the user
            -> m TokenText
-issueToken authenticateState isAuthAdmin user =
+issueToken authenticateState authenticateConfig user =
   do ssecret <- getOrGenSharedSecret authenticateState (user ^. userId)
-     admin   <- liftIO $ isAuthAdmin (user ^. userId)
-     let claims = def { unregisteredClaims =
+     admin   <- liftIO $ (authenticateConfig ^. isAuthAdmin) (user ^. userId)
+     now <- liftIO getCurrentTime
+     let claims = def { exp = intDate $ utcTimeToPOSIXSeconds (addUTCTime (60*60*24*30) now)
+                      , unregisteredClaims =
                            Map.fromList [ ("user"     , toJSON user)
                                         , ("authAdmin", toJSON admin)
-                                        ] }
+                                        ]
+                      }
      return $ encodeSigned HS256 (secret $ _unSharedSecret ssecret) claims
 
 -- | decode and verify the `TokenText`. If successful, return the
 -- `Token` otherwise `Nothing`.
 decodeAndVerifyToken :: (MonadIO m) =>
                         AcidState AuthenticateState
+                     -> UTCTime
                      -> TokenText
                      -> m (Maybe (Token, JWT VerifiedJWT))
-decodeAndVerifyToken authenticateState token =
+decodeAndVerifyToken authenticateState now token =
   do -- decode unverified token
      let mUnverified = decode token
      case mUnverified of
@@ -598,13 +663,19 @@
                         -- finally we can verify all the claims
                         case verify (secret (_unSharedSecret ssecret)) unverified of
                           Nothing -> return Nothing
-                          (Just verified) ->
-                            case Map.lookup "authAdmin" (unregisteredClaims (claims verified)) of
-                              Nothing -> return (Just (Token u False, verified))
-                              (Just a) ->
-                                case fromJSON a of
-                                  (Error _) -> return (Just (Token u False, verified))
-                                  (Success b) -> return (Just (Token u b, verified))
+                          (Just verified) -> -- check expiration
+                            case exp (claims verified) of
+                            -- exp field missing, expire now
+                              Nothing -> return Nothing
+                              (Just exp') ->
+                                if (utcTimeToPOSIXSeconds now) > (secondsSinceEpoch exp')
+                                then return Nothing
+                                else case Map.lookup "authAdmin" (unregisteredClaims (claims verified)) of
+                                       Nothing -> return (Just (Token u False, verified))
+                                       (Just a) ->
+                                           case fromJSON a of
+                                             (Error _) -> return (Just (Token u False, verified))
+                                             (Success b) -> return (Just (Token u b, verified))
 
 ------------------------------------------------------------------------------
 -- Token in a Cookie
@@ -619,13 +690,14 @@
 -- see also: `issueToken`
 addTokenCookie :: (Happstack m) =>
                   AcidState AuthenticateState
-               -> (UserId -> IO Bool)
+               -> AuthenticateConfig
                -> User
                -> m TokenText
-addTokenCookie authenticateState isAuthAdmin user =
-  do token <- issueToken authenticateState isAuthAdmin user
+addTokenCookie authenticateState authenticateConfig user =
+  do token <- issueToken authenticateState authenticateConfig user
      s <- rqSecure <$> askRq -- FIXME: this isn't that accurate in the face of proxies
      addCookie (MaxAge (60*60*24*30)) ((mkCookie authCookieName (Text.unpack token)) { secure = s })
+--     addCookie (MaxAge 60) ((mkCookie authCookieName (Text.unpack token)) { secure = s })
      return token
 
 -- | delete the `Token` `Cookie`
@@ -643,7 +715,9 @@
   do mToken <- optional $ lookCookieValue authCookieName
      case mToken of
        Nothing      -> return Nothing
-       (Just token) -> decodeAndVerifyToken authenticateState (Text.pack token)
+       (Just token) ->
+           do now <- liftIO getCurrentTime
+              decodeAndVerifyToken authenticateState now (Text.pack token)
 
 
 ------------------------------------------------------------------------------
@@ -660,7 +734,8 @@
        Nothing -> return Nothing
        (Just auth') ->
          do let auth = B.drop 7 auth'
-            decodeAndVerifyToken authenticateState (Text.decodeUtf8 auth)
+            now <- liftIO getCurrentTime
+            decodeAndVerifyToken authenticateState now (Text.decodeUtf8 auth)
 
 ------------------------------------------------------------------------------
 -- Token in a Header or Cookie
@@ -752,3 +827,4 @@
                          -> RouteT AuthenticateURL m a
 nestAuthenticationMethod authenticationMethod =
   nestURL $ \methodURL -> AuthenticationMethods $ Just (authenticationMethod, toPathSegments methodURL)
+
diff --git a/Happstack/Authenticate/OpenId/Controllers.hs b/Happstack/Authenticate/OpenId/Controllers.hs
--- a/Happstack/Authenticate/OpenId/Controllers.hs
+++ b/Happstack/Authenticate/OpenId/Controllers.hs
@@ -39,322 +39,58 @@
   -> JStat
 openIdCtrlJs mRealm showURL =
   [jmacro|
-    var openId = angular.module('openId', ['happstackAuthentication']);
-    var openIdWindow;
-    tokenCB = function (token) { alert('tokenCB: ' + token); };
-
-    openId.controller('OpenIdCtrl', ['$scope','$http','$window', '$location', 'userService', function ($scope, $http, $window, $location, userService) {
-        $scope.openIdRealm = { srOpenIdRealm: `(fromMaybe "" mRealm)` };
-//      $scope.isAuthenticated = userService.getUser().isAuthenticated;
-
-//      $scope.$watch(function () { return userService.getUser().isAuthenticated; }, function(newVal, oldVal) { $scope.isAuthenticated = newVal; });
-
-      $scope.openIdWindow = function (providerUrl) {
-//        openIdWindow = window.open(`(showURL ReturnTo [])`, "_blank", "toolbar=0");
-        tokenCB = function(token) { var u = userService.updateFromToken(token); $scope.isAuthenticated = u.isAuthenticated; $scope.$apply(); };
-        openIdWindow = window.open(providerUrl, "_blank", "toolbar=0");
-      };
-
-      $scope.setOpenIdRealm = function (setRealmUrl) {
-        $http.post(setRealmUrl, $scope.openIdRealm).
-          success(function(datum, status, headers,config) {
-           $scope.set_openid_realm_msg = 'Realm Updated.'; // FIXME -- I18N
-          }).
-          error(function (datum, status, headers, config) {
-            $scope.set_openid_realm_msg = datum.error;
-      })};
-    }]);
-
-    openId.directive('openidGoogle', ['$rootScope', function ($rootScope) {
-      return {
-        restrict: 'E',
-        replace: true,
-        templateUrl: `(showURL (Partial UsingGoogle) [])`
-      };
-    }]);
-
-    openId.directive('openidYahoo', ['$rootScope', function ($rootScope) {
-      return {
-        restrict: 'E',
-        replace: true,
-        templateUrl: `(showURL (Partial UsingYahoo) [])`
-      };
-    }]);
-
-    openId.directive('openidRealm', ['$rootScope', function ($rootScope) {
-      return {
-        restrict: 'E',
-        templateUrl: `(showURL (Partial RealmForm) [])`
-      };
-    }]);
-
-  |]
-
-{-
-openIdCtrlJs showURL = [jmacro|
-  {
-    //this is used to parse the profile
-    function url_base64_decode(str) {
-      var output = str.replace('-', '+').replace('_', '/');
-      switch (output.length % 4) {
-      case 0:
-        break;
-      case 2:
-        output += '==';
-        break;
-      case 3:
-        output += '=';
-        break;
-      default:
-        throw 'Illegal base64url string!';
-      }
-      return window.atob(output); //polifyll https://github.com/davidchambers/Base64.js
-    }
-
-    var happstackAuthentication = angular.module('happstackAuthentication', []);
-    happstackAuthentication.factory('userService', ['$rootScope', function ($rootScope) {
-      var defaultUser = { isAuthenticated: false,
-                          username:        '<unset>',
-                          token:           null
-                        };
-
-      var service = {
-        userCache: defaultUser,
-        setUser: function(u) {
-//          alert('setUser:' + JSON.stringify(u));
-          userCache = u;
-          localStorage.setItem('user', JSON.stringify(u));
-        },
-
-        getUser: function() {
-          var item = localStorage.getItem('user');
-          if (item) {
-//            alert('getUser: ' + item);
-            var user = JSON.parse(item);
-            return(user);
-          }
-        },
-
-        clearUser: function () {
-          userCache = defaultUser;
-          this.setUser(defaultUser);
-        }
-      };
-
-      if (!localStorage.getItem('user')) {
-        service.clearUser();
-      }
-
-      return service;
-    }]);
-
-    var openId = angular.module('openId', ['happstackAuthentication']);
-
-    openId.controller('UsernamePasswordCtrl', ['$scope','$http','$window', '$location', 'userService', function ($scope, $http, $window, $location, userService) {
-      $scope.isAuthenticated = userService.getUser().isAuthenticated;
-
-      $scope.login = function () {
-        $http.
-          post(`(showURL Token [])`, $scope.user).
-          success(function (datum, status, headers, config) {
-            var encodedClaims = datum.token.split('.')[1];
-            var claims = JSON.parse(url_base64_decode(encodedClaims));
-            $scope.username_password_error = '';
-
-            var u = userService.getUser();
-            u.isAuthenticated   = true;
-            $scope.isAuthenticated = true;
-            u.username = $scope.user.user; // FIXME: this should come from the token
-            u.token  = datum.token;
-            userService.setUser(u);
-          }).
-          error(function (datum, status, headers, config) {
-            // Erase the token if the user fails to log in
-            userService.clearUser();
-            $scope.isAuthenticated = false;
-
-            // Handle login errors here
-            $scope.username_password_error = datum.error;
-          });
-      };
-
-      $scope.logout = function () {
-        userService.clearUser();
-        $scope.isAuthenticated = false;
-      };
-
-      $scope.signupPassword = function () {
-        $scope.signup.naUser.userId = 0;
-        $http.
-          post(`(showURL (Account Nothing) [])`, $scope.signup).
-          success(function (datum, status, headers, config) {
-            $scope.signup_error = 'Account Created'; // FIXME -- I18N
-            $scope.signup = {};
-          }).
-          error(function (datum, status, headers, config) {
-            $scope.signup_error = datum.error;
-          });
-      };
-
-      $scope.changePassword = function (url) {
-        var u = userService.getUser();
-        if (u.isAuthenticated) {
-          $http.
-            post(url, $scope.password).
-            success(function (datum, status, headers, config) {
-            $scope.change_password_error = 'Password Changed.'; // FIXME -- I18N
-            $scope.password = {};
-          }).
-          error(function (datum, status, headers, config) {
-            $scope.change_password_error = datum.error;
-          });
-        } else {
-            $scope.change_password_error = 'Not Authenticated.'; // FIXME -- I18N
-        }
-      };
-
-      $scope.requestResetPassword = function () {
-        $http.post(`(showURL PasswordRequestReset [])`, $scope.requestReset).
-          success(function (datum, status, headers, config) {
-            $scope.request_reset_password_msg = datum;
-          }).
-          error(function (datum, status, headers, config) {
-            $scope.request_reset_password_msg = datum.error;
-          });
-      };
-
-      $scope.resetPassword = function () {
-        var resetToken = $location.search().reset_token;
-        if (resetToken) {
-          $scope.reset.rpResetToken = resetToken;
-          $http.post(`(showURL PasswordReset [])`, $scope.reset).
-            success(function (datum, status, headers, config) {
-              $scope.reset_password_msg = datum;
-            }).
-            error(function (datum, status, headers, config) {
-              $scope.reset_password_msg = datum.error;
-            });
-        } else {
-          $scope.reset_password_msg = "reset token not found."; // FIXME -- I18N
-        }
-      };
-
-    }]);
-
-    openId.factory('authInterceptor', ['$rootScope', '$q', '$window', 'userService', function ($rootScope, $q, $window, userService) {
-      return {
-        request: function (config) {
-          config.headers = config.headers || {};
-          u = userService.getUser();
-          if (u && u.token) {
-            config.headers.Authorization = 'Bearer ' + u.token;
-          }
-          return config;
-        },
-        responseError: function (rejection) {
-          if (rejection.status === 401) {
-            // handle the case where the user is not authenticated
-          }
-          return $q.reject(rejection);
-        }
-      };
-    }]);
-
-    openId.config(['$httpProvider', function ($httpProvider) {
-      $httpProvider.interceptors.push('authInterceptor');
-    }]);
-
-    // upAuthenticated directive
-    openId.directive('upAuthenticated', ['$rootScope', 'userService', function ($rootScope, userService) {
-      return {
-        restrict: 'A',
-        link:     function (scope, element, attrs) {
-          var prevDisp = element.css('display');
-          $rootScope.$watch(function () { return userService.getUser().isAuthenticated; },
-                            function(auth) {
-                              if (auth != (attrs.upAuthenticated == 'true')) {
-                                element.css('display', 'none');
-                              } else {
-                                element.css('display', prevDisp);
-                              }
-                            });
-        }
-      };
-    }]);
-
-    // upLoginInline directive
-    openId.directive('upLoginInline', ['$rootScope', 'userService', function ($rootScope, userService) {
-      return {
-        restrict: 'E',
-//        replace: true,
-        templateUrl: `(showURL (Partial LoginInline) [])`
-      };
-    }]);
-
-    // upChangePassword directive
-    openId.directive('upChangePassword', ['$rootScope', '$http', '$compile', 'userService', function ($rootScope, $http, $compile, userService) {
-
-      function link(scope, element, attrs) {
-        $rootScope.$watch(function() { return userService.getUser().isAuthenticated; },
-                          function(auth) {
-                              if (auth == true) {
-                                $http.get(`(showURL (Partial ChangePassword) [])`).
-                                  success(function(datum, status, headers, config) {
-                                    element.empty();
-                                    var newElem = angular.element(datum);
-                                    element.append(newElem);
-                                    $compile(newElem)(scope);
-                                  });
-                              } else {
-                                element.empty();
-                              }
-                          });
-
-      }
+   var openId = angular.module('openId', ['happstackAuthentication']);
+   var openIdWindow;
+   tokenCB = function (token) { alert('tokenCB: ' + token); };
 
-      return {
-        restrict: 'E',
-        link: link
-//        templateUrl: `(showURL (Partial ChangePassword) [])`
-//        template: "<div ng-include=\"'" + `(showURL (Partial ChangePassword) [])` + "'\"></div>"
-//        template: "<div ng-include=\"'/authenticate/authentication-methods/password/partial/change-password'\"></div>"
-      };
- /*
-      return {
-        restrict: 'E',
-        templateUrl: `(showURL (Partial ChangePassword) [])`
-      };
-*/
-    }]);
+   openId.controller('OpenIdCtrl', ['$scope','$http','$window', '$location', 'userService', function ($scope, $http, $window, $location, userService)
+     { $scope.openIdRealm = { srOpenIdRealm: `(fromMaybe "" mRealm)` };
 
+       $scope.openIdWindow = function (providerUrl) {
+         tokenCB = function(token) { var u = userService.updateFromToken(token); $scope.isAuthenticated = u.isAuthenticated; $scope.$apply(); };
+         openIdWindow = window.open(providerUrl, "_blank", "toolbar=0");
+       };
 
-    // upRequestResetPassword directive
-    openId.directive('upRequestResetPassword', [function () {
-      return {
-        restrict: 'E',
-//        replace: true,
-        templateUrl: `(showURL (Partial RequestResetPasswordForm) [])`
-      };
-    }]);
+       $scope.setOpenIdRealm = function (setRealmUrl) {
+         function callback(datum, status, headers, config) {
+           if (datum == null) {
+             $scope.username_password_error = 'error communicating with the server.';
+           } else {
+             if (datum.jrStatus == "Ok") {
+               $scope.set_openid_realm_msg = 'Realm Updated.'; // FIXME -- I18N
+//               $scope.openIdRealm = '';
+             } else {
+               $scope.set_open_id_realm_msg = datum.jrData;
+             }
+           }
+         };
 
-    // upResetPassword directive
-    openId.directive('upResetPassword', [function () {
-      return {
-        restrict: 'E',
-//        replace: true,
-        templateUrl: `(showURL (Partial ResetPasswordForm) [])`
-      };
-    }]);
+         $http.post(setRealmUrl, $scope.openIdRealm).
+           success(callback).
+           error(callback);
+       };
+     }]);
 
-    openId.directive('upSignupPassword', [function () {
-      return {
-        restrict: 'E',
-//        replace: true,
-        templateUrl: `(showURL (Partial SignupPassword) [])`
-      };
+   openId.directive('openidGoogle', ['$rootScope', function ($rootScope) {
+     return {
+       restrict: 'E',
+       replace: true,
+       templateUrl: `(showURL (Partial UsingGoogle) [])`
+     };
     }]);
 
+   openId.directive('openidYahoo', ['$rootScope', function ($rootScope) {
+     return {
+       restrict: 'E',
+       replace: true,
+       templateUrl: `(showURL (Partial UsingYahoo) [])`
+     };
+   }]);
 
-  }
+   openId.directive('openidRealm', ['$rootScope', function ($rootScope) {
+     return {
+       restrict: 'E',
+       templateUrl: `(showURL (Partial RealmForm) [])`
+     };
+   }]);
   |]
--}
diff --git a/Happstack/Authenticate/OpenId/Core.hs b/Happstack/Authenticate/OpenId/Core.hs
--- a/Happstack/Authenticate/OpenId/Core.hs
+++ b/Happstack/Authenticate/OpenId/Core.hs
@@ -25,7 +25,7 @@
 import qualified Data.Map          as Map
 import Data.UserId                 (UserId)
 import GHC.Generics                (Generic)
-import Happstack.Authenticate.Core (AuthenticateState, CoreError(..), CreateAnonymousUser(..), GetUserByUserId(..), HappstackAuthenticateI18N(..), addTokenCookie, getToken, jsonOptions, toJSONError, toJSONSuccess, toJSONResponse, tokenIsAuthAdmin, userId)
+import Happstack.Authenticate.Core (AuthenticateConfig(..), AuthenticateState, CoreError(..), CreateAnonymousUser(..), GetUserByUserId(..), HappstackAuthenticateI18N(..), addTokenCookie, getToken, jsonOptions, toJSONError, toJSONSuccess, toJSONResponse, tokenIsAuthAdmin, userId)
 import Happstack.Authenticate.OpenId.URL
 import Happstack.Server            (RqBody(..), Happstack, Method(..), Response, askRq, unauthorized, badRequest, internalServerError, forbidden, lookPairsBS, method, resp, takeRequestBody, toResponse, toResponseBS, ok)
 import Language.Javascript.JMacro
@@ -172,10 +172,10 @@
 
 token :: (Alternative m, Happstack m) =>
          AcidState AuthenticateState
-      -> (UserId -> IO Bool)
+      -> AuthenticateConfig
       -> AcidState OpenIdState
       -> m Response
-token authenticateState isAuthAdmin openIdState =
+token authenticateState authenticateConfig openIdState =
     do identifier <- getIdentifier
        mUserId <- query' openIdState (IdentifierToUserId identifier)
        mUser <- case mUserId of
@@ -192,7 +192,7 @@
                   return (Just u)
        case mUser of
          Nothing     -> internalServerError $ toJSONError $ CoreError InvalidUserId
-         (Just user) -> do token <- addTokenCookie authenticateState isAuthAdmin user
+         (Just user) -> do token <- addTokenCookie authenticateState authenticateConfig user
                            let tokenBS = TL.encodeUtf8 $ TL.fromStrict token
 --                           ok $ toResponse token
                            ok $ toResponseBS "text/html" $ "<html><head><script type='text/javascript'>window.opener.tokenCB('" <> tokenBS <> "'); window.close();</script></head><body></body></html>"
diff --git a/Happstack/Authenticate/OpenId/Partials.hs b/Happstack/Authenticate/OpenId/Partials.hs
--- a/Happstack/Authenticate/OpenId/Partials.hs
+++ b/Happstack/Authenticate/OpenId/Partials.hs
@@ -90,6 +90,7 @@
      [hsx|
       <div ng-show="claims.authAdmin">
        <form ng-submit=setOpenIdRealmFn role="form">
+        <div class="form-group">{{set_openid_realm_msg}}</div>
         <div class="form-group">
          <label for="openid-realm"><% OpenIdRealmMsg %></label>
          <input class="form-control" ng-model="openIdRealm.srOpenIdRealm" type="text" id="openid-realm" name="openIdRealm" />
@@ -98,132 +99,5 @@
          <input class="form-control" type="submit" value=SetRealmMsg />
         </div>
        </form>
-       <div>{{set_openid_realm_msg}}</div>
       </div>
      |]
-
-{-
-signupPasswordForm :: (Functor m, Monad m) =>
-                      Partial m XML
-signupPasswordForm =
-     [hsx|
-       <form ng-submit="signupPassword()" role="form">
-        <div class="form-group">
-         <label class="sr-only" for="su-username"><% UsernameMsg %></label>
-         <input class="form-control" ng-model="signup.naUser.username" type="text" id="username" name="su-username" placeholder=UsernameMsg />
-        </div>
-        <div class="form-group">
-         <label class="sr-only" for="su-email"><% EmailMsg %></label>
-         <input class="form-control" ng-model="signup.naUser.email" type="email" id="su-email" name="email" placeholder=EmailMsg />
-        </div>
-        <div class="form-group">
-         <label class="sr-only" for="su-password"><% PasswordMsg %></label>
-         <input class="form-control" ng-model="signup.naPassword" type="password" id="su-password" name="su-pass" placeholder=PasswordMsg />
-        </div>
-        <div class="form-group">
-         <label class="sr-only" for="su-password-confirm"><% PasswordConfirmationMsg %></label>
-         <input class="form-control" ng-model="signup.naPasswordConfirm" type="password" id="su-password-confirm" name="su-pass-confirm" placeholder=PasswordConfirmationMsg />
-        </div>
-        <div class="form-group">
-         <input class="form-control" type="submit" value=SignUpMsg />
-        </div>
-        <div>{{signup_error}}</div>
-       </form>
-  |]
-
-usernamePasswordForm :: (Functor m, Monad m) =>
-                        Partial m XML
-usernamePasswordForm = [hsx|
-    <div>
-     <div ng-show="!isAuthenticated">
-      <form ng-submit="login()" role="form" class="navbar-form navbar-right">
-       <div class="form-group">
-        <label class="sr-only" for="username"><% UsernameMsg %> </label>
-        <input class="form-control" ng-model="user.user" type="text" id="username" name="user" placeholder=UsernameMsg />
-       </div><% " " :: Text %>
-       <div class="form-group">
-        <label class="sr-only" for="password"><% PasswordMsg %></label>
-        <input class="form-control" ng-model="user.password" type="password" id="password" name="pass" placeholder=PasswordMsg />
-       </div><% " " :: Text %>
-       <div class="form-group">
-       <input class="form-control" type="submit" value=SignInMsg />
-       </div>
-      </form>
-      <div>{{username_password_error}}</div>
-     </div>
-      <div ng-show="isAuthenticated">
-       <a class="navbar-right" ng-click="logout()" href=""><% LogoutMsg %></a>
-     </div>
-    </div>
-  |]
-
-changePasswordForm :: (Functor m, MonadIO m) =>
-                      UserId
-                   -> Partial m XML
-changePasswordForm userId =
-  do url <- lift $ nestPasswordURL $ showURL (Account (Just (userId, Password)))
-     let changePasswordFn = "changePassword('" <> url <> "')"
-     [hsx|
-       <form ng-submit=changePasswordFn role="form">
-        <div class="form-group">
-         <label class="sr-only" for="password"><% OldPasswordMsg %></label>
-         <input class="form-control" ng-model="password.cpOldPassword" type="password" id="old-password" name="old-pass" placeholder=OldPasswordMsg />
-        </div>
-        <div class="form-group">
-         <label class="sr-only" for="password"><% NewPasswordMsg %></label>
-         <input class="form-control" ng-model="password.cpNewPassword" type="password" id="new-password" name="new-pass" placeholder=NewPasswordMsg />
-        </div>
-        <div class="form-group">
-         <label class="sr-only" for="password"><% NewPasswordConfirmationMsg %></label>
-         <input class="form-control" ng-model="password.cpNewPasswordConfirm" type="password" id="new-password-confirm" name="new-pass-confirm" placeholder=NewPasswordConfirmationMsg />
-        </div>
-        <div class="form-group">
-         <input class="form-control" type="submit" value=ChangePasswordMsg />
-        </div>
-        <div>{{change_password_error}}</div>
-       </form>
-
-     |]
-
-requestResetPasswordForm :: (Functor m, MonadIO m) =>
-                            Partial m XML
-requestResetPasswordForm =
-  do -- url <- lift $ nestPasswordURL $ showURL PasswordReset
-     -- let changePasswordFn = "resetPassword('" <> url <> "')"
-     [hsx|
-      <div>
-       <form ng-submit="requestResetPassword()" role="form">
-        <div class="form-group">
-         <label class="sr-only" for="reset-username"><% UsernameMsg %></label>
-         <input class="form-control" ng-model="requestReset.rrpUsername" type="text" id="reset-username" name="username" placeholder=UsernameMsg />
-        </div>
-        <div class="form-group">
-         <input class="form-control" type="submit" value=RequestPasswordResetMsg />
-        </div>
-       </form>
-       <div>{{request_reset_password_msg}}</div>
-      </div>
-     |]
-
-resetPasswordForm :: (Functor m, MonadIO m) =>
-                     Partial m XML
-resetPasswordForm =
-  [hsx|
-      <div>
-       <form ng-submit="resetPassword()" role="form">
-        <div class="form-group">
-         <label class="sr-only" for="reset-password"><% PasswordMsg %></label>
-         <input class="form-control" ng-model="reset.rpPassword" type="password" id="reset-password" name="reset-password" placeholder=PasswordMsg />
-        </div>
-        <div class="form-group">
-         <label class="sr-only" for="reset-password-confirm"><% PasswordConfirmationMsg %></label>
-         <input class="form-control" ng-model="reset.rpPasswordConfirm" type="password" id="reset-password-confirm" name="reset-password-confirm" placeholder=PasswordConfirmationMsg />
-        </div>
-        <div class="form-group">
-         <input class="form-control" type="submit" value=ChangePasswordMsg />
-        </div>
-       </form>
-       <div>{{reset_password_msg}}</div>
-      </div>
-  |]
--}
diff --git a/Happstack/Authenticate/OpenId/Route.hs b/Happstack/Authenticate/OpenId/Route.hs
--- a/Happstack/Authenticate/OpenId/Route.hs
+++ b/Happstack/Authenticate/OpenId/Route.hs
@@ -9,7 +9,7 @@
 import Data.Acid.Local       (createCheckpointAndClose, openLocalStateFrom)
 import Data.Text             (Text)
 import Data.UserId           (UserId)
-import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod, AuthenticateState, AuthenticateURL, CoreError(..), toJSONError, toJSONResponse)
+import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod, AuthenticateConfig, AuthenticateState, AuthenticateURL, CoreError(..), toJSONError, toJSONResponse)
 import Happstack.Authenticate.OpenId.Core (GetOpenIdRealm(..), OpenIdError(..), OpenIdState, initialOpenIdState, realm, token)
 import Happstack.Authenticate.OpenId.Controllers (openIdCtrl)
 import Happstack.Authenticate.OpenId.URL (OpenIdURL(..), openIdAuthenticationMethod, nestOpenIdURL)
@@ -31,11 +31,11 @@
 
 routeOpenId :: (Happstack m) =>
                AcidState AuthenticateState
-            -> (UserId -> IO Bool)
+            -> AuthenticateConfig
             -> AcidState OpenIdState
             -> [Text]
             -> RouteT AuthenticateURL (ReaderT [Lang] m) Response
-routeOpenId authenticateState isAuthAdmin openIdState pathSegments =
+routeOpenId authenticateState authenticateConfig openIdState pathSegments =
   case parseSegments fromPathSegments pathSegments of
     (Left _) -> notFound $ toJSONError URLDecodeFailed
     (Right url) ->
@@ -48,7 +48,7 @@
              realm <- query' openIdState GetOpenIdRealm
              forwardURL <- liftIO $ withManager $ getForwardUrl providerURL returnURL realm [] -- [("Email", "http://schema.openid.net/contact/email")]
              seeOther forwardURL (toResponse ())
-        ReturnTo -> token authenticateState isAuthAdmin openIdState
+        ReturnTo -> token authenticateState authenticateConfig openIdState
         Realm    -> realm authenticateState openIdState
 
 ------------------------------------------------------------------------------
@@ -57,9 +57,9 @@
 
 initOpenId :: FilePath
            -> AcidState AuthenticateState
-           -> (UserId -> IO Bool)
+           -> AuthenticateConfig
            -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)
-initOpenId basePath authenticateState isAuthAdmin =
+initOpenId basePath authenticateState authenticateConfig =
   do openIdState <- openLocalStateFrom (combine basePath "openId") initialOpenIdState
      let shutdown = \normal ->
            if normal
@@ -69,6 +69,6 @@
            do langsOveride <- queryString $ lookTexts' "_LANG"
               langs        <- bestLanguage <$> acceptLanguage
               mapRouteT (flip runReaderT (langsOveride ++ langs)) $
-               routeOpenId authenticateState isAuthAdmin openIdState pathSegments
+               routeOpenId authenticateState authenticateConfig openIdState pathSegments
      return (shutdown, (openIdAuthenticationMethod, authenticationHandler), openIdCtrl authenticateState openIdState)
 
diff --git a/Happstack/Authenticate/Password/Controllers.hs b/Happstack/Authenticate/Password/Controllers.hs
--- a/Happstack/Authenticate/Password/Controllers.hs
+++ b/Happstack/Authenticate/Password/Controllers.hs
@@ -21,86 +21,160 @@
     var usernamePassword = angular.module('usernamePassword', ['happstackAuthentication']);
 
     usernamePassword.controller('UsernamePasswordCtrl', ['$scope','$http','$window', '$location', 'userService', function ($scope, $http, $window, $location, userService) {
-/*      $scope.isAuthenticated = userService.getUser().isAuthenticated;
 
-      $scope.$watch(function () { return userService.getUser().isAuthenticated; }, function(newVal, oldVal) { $scope.isAuthenticated = newVal; });
-*/
+      // login()
+      emptyUser = function() {
+        return { user: '',
+                 password: ''
+               };
+      };
+
+      $scope.user = emptyUser();
       $scope.login = function () {
+        function callback(datum, status, headers, config) {
+          if (datum == null) {
+            $scope.username_password_error = 'error communicating with the server.';
+          } else {
+            if (datum.jrStatus == "Ok") {
+              $scope.username_password_error = '';
+              userService.updateFromToken(datum.jrData.token);
+            } else {
+              userService.clearUser();
+              $scope.username_password_error = datum.jrData;
+            }
+          }
+        };
         $http.
           post(`(showURL Token [])`, $scope.user).
-          success(function (datum, status, headers, config) {
-            $scope.username_password_error = '';
-//            $scope.isAuthenticated = true;
-            userService.updateFromToken(datum.token);
-          }).
-          error(function (datum, status, headers, config) {
-            // Erase the token if the user fails to log in
-            userService.clearUser();
-//            $scope.isAuthenticated = false;
+          success(callback).
+          error(callback);
+      };
 
-            // Handle login errors here
-            $scope.username_password_error = datum.error;
-          });
+      // signupPassword()
+      emptySignup = function () {
+        return { naUser: { username: '',
+                           email: ''
+                         },
+                 naPassword: '',
+                 naPasswordConfirm: ''
+               };
       };
+      $scope.signup = emptySignup();
 
       $scope.signupPassword = function () {
         $scope.signup.naUser.userId = 0;
+
+        function callback(datum, status, headers, config) {
+          if (datum == null) {
+            $scope.username_password_error = 'error communicating with server.';
+          } else {
+            if (datum.jrStatus == "Ok") {
+              $scope.signup_error = 'Account Created'; // FIXME -- I18N
+              $scope.signup = emptySignup();
+            } else {
+              $scope.signup_error = datum.jrData;
+            }
+          }
+        };
+
         $http.
           post(`(showURL (Account Nothing) [])`, $scope.signup).
-          success(function (datum, status, headers, config) {
-            $scope.signup_error = 'Account Created'; // FIXME -- I18N
-            $scope.signup = {};
-          }).
-          error(function (datum, status, headers, config) {
-            $scope.signup_error = datum.error;
-          });
+          success(callback).
+          error(callback);
       };
 
+      // changePassword()
+      emptyPassword = function () {
+        return { cpOldPassword: '',
+                 cpNewPassword: '',
+                 cpNewPasswordConfirm: ''
+               };
+      };
+
+      $scope.password = emptyPassword();
       $scope.changePassword = function (url) {
         var u = userService.getUser();
+
+        function callback(datum, status, headers, config) {
+          if (datum == null) {
+            $scope.username_password_error = 'error communicating with server.';
+          } else {
+            if (datum.jrStatus == "Ok") {
+              $scope.change_password_error = 'Password Changed.'; // FIXME -- I18N
+              $scope.password = emptyPassword();
+            } else {
+              $scope.change_password_error = datum.jrData;
+            }
+          }
+        };
+
         if (u.isAuthenticated) {
           $http.
             post(url, $scope.password).
-            success(function (datum, status, headers, config) {
-            $scope.change_password_error = 'Password Changed.'; // FIXME -- I18N
-            $scope.password = {};
-          }).
-          error(function (datum, status, headers, config) {
-            $scope.change_password_error = datum.error;
-          });
+            success(callback).
+            error(callback);
         } else {
-            $scope.change_password_error = 'Not Authenticated.'; // FIXME -- I18N
+          $scope.change_password_error = 'Not Authenticated.'; // FIXME -- I18N
         }
       };
 
+      // requestResetPassword()
+      requestResetEmpty = function () {
+        return { rrpUsername: '' };
+      };
+      $scope.requestReset = requestResetEmpty();
       $scope.requestResetPassword = function () {
+        function callback(datum, status, headers, config) {
+          if (datum == null) {
+            $scope.request_reset_password_msg = 'error communicating with the server.';
+          } else {
+            if (datum.jrStatus == "Ok") {
+              $scope.request_reset_password_msg = datum.jrData;
+              $scope.requestReset = requestResetEmpty();
+            } else {
+              $scope.request_reset_password_msg = datum.jrData;
+            }
+          }
+        }
+
         $http.post(`(showURL PasswordRequestReset [])`, $scope.requestReset).
-          success(function (datum, status, headers, config) {
-            $scope.request_reset_password_msg = datum;
-          }).
-          error(function (datum, status, headers, config) {
-            $scope.request_reset_password_msg = datum.error;
-          });
+          success(callback).
+          error(callback);
       };
 
+      // resetPassword()
+      resetEmpty = function () {
+        return { rpPassword: '',
+                 rpPasswordConfirm: ''
+               };
+      };
+      $scope.reset = resetEmpty();
       $scope.resetPassword = function () {
+          function callback(datum, status, headers, config) {
+              if (datum == null) {
+                  $scope.reset_password_msg = 'error communicating with the server.';
+              } else {
+                  if (datum.jrStatus == "Ok") {
+                    $scope.reset_password_msg = datum.jrData;
+                    $scope.reset = resetEmpty();
+                  } else {
+                      $scope.reset_password_msg = datum.jrData;
+                  }
+              }
+          }
+
         var resetToken = $location.search().reset_token;
         if (resetToken) {
           $scope.reset.rpResetToken = resetToken;
           $http.post(`(showURL PasswordReset [])`, $scope.reset).
-            success(function (datum, status, headers, config) {
-              $scope.reset_password_msg = datum;
-            }).
-            error(function (datum, status, headers, config) {
-              $scope.reset_password_msg = datum.error;
-            });
+            success(callback).
+            error(callback);
         } else {
           $scope.reset_password_msg = "reset token not found."; // FIXME -- I18N
         }
       };
-
     }]);
-
+    /*
     usernamePassword.factory('authInterceptor', ['$rootScope', '$q', '$window', 'userService', function ($rootScope, $q, $window, userService) {
       return {
         request: function (config) {
@@ -114,6 +188,8 @@
         responseError: function (rejection) {
           if (rejection.status === 401) {
             // handle the case where the user is not authenticated
+            userService.clearUser();
+            document.cookie = 'atc=; path=/; expires=Thu, 01-Jan-70 00:00:01 GMT;';
           }
           return $q.reject(rejection);
         }
@@ -123,7 +199,7 @@
     usernamePassword.config(['$httpProvider', function ($httpProvider) {
       $httpProvider.interceptors.push('authInterceptor');
     }]);
-
+     */
     // upAuthenticated directive
     usernamePassword.directive('upAuthenticated', ['$rootScope', 'userService', function ($rootScope, userService) {
       return {
diff --git a/Happstack/Authenticate/Password/Core.hs b/Happstack/Authenticate/Password/Core.hs
--- a/Happstack/Authenticate/Password/Core.hs
+++ b/Happstack/Authenticate/Password/Core.hs
@@ -30,7 +30,7 @@
 import Data.Time.Clock.POSIX          (getPOSIXTime)
 import Data.UserId (UserId)
 import GHC.Generics (Generic)
-import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod(..), AuthenticateState(..), AuthenticateURL, CoreError(..), CreateUser(..), Email(..), GetUserByUsername(..), HappstackAuthenticateI18N(..), SharedSecret(..), User(..), Username(..), GetSharedSecret(..), addTokenCookie, email, getToken, getOrGenSharedSecret, issueToken, jsonOptions, userId, username, toJSONResponse, toJSONError, tokenUser)
+import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod(..), AuthenticateState(..), AuthenticateConfig, usernameAcceptable, requireEmail, AuthenticateURL, CoreError(..), CreateUser(..), Email(..), unEmail, GetUserByUsername(..), HappstackAuthenticateI18N(..), SharedSecret(..), User(..), Username(..), GetSharedSecret(..), addTokenCookie, email, getToken, getOrGenSharedSecret, jsonOptions, userId, username, toJSONSuccess, toJSONResponse, toJSONError, tokenUser)
 import Happstack.Authenticate.Password.URL (AccountURL(..))
 import Happstack.Server
 import HSP.JMacro
@@ -38,6 +38,7 @@
 import Network.HTTP.Types              (toQuery, renderQuery)
 import Network.Mail.Mime               (Address(..), Mail, simpleMail', renderMail', renderSendMail, sendmail)
 import System.FilePath                 (combine)
+import qualified Text.Email.Validate   as Email
 import Text.Shakespeare.I18N           (RenderMessage(..), Lang, mkMessageFor)
 import qualified Web.JWT               as JWT
 import Web.JWT                         (Algorithm(HS256), JWT, VerifiedJWT, JWTClaimsSet(..), encodeSigned, claims, decode, decodeAndVerifySignature, intDate, secret, secondsSinceEpoch, verify)
@@ -45,6 +46,18 @@
 import Web.Routes.TH
 
 ------------------------------------------------------------------------------
+-- PasswordConfig
+------------------------------------------------------------------------------
+
+data PasswordConfig = PasswordConfig
+    { _resetLink :: Text
+    , _domain :: Text
+    , _passwordAcceptable :: Text -> Maybe Text
+    }
+    deriving (Typeable, Generic)
+makeLenses ''PasswordConfig
+
+------------------------------------------------------------------------------
 -- PasswordError
 ------------------------------------------------------------------------------
 
@@ -58,6 +71,7 @@
   | MissingResetToken
   | InvalidResetToken
   | PasswordMismatch
+  | UnacceptablePassword { passwordErrorMessageMsg :: Text }
   | CoreError { passwordErrorMessageE :: CoreError }
     deriving (Eq, Ord, Read, Show, Data, Typeable, Generic)
 instance ToJSON   PasswordError where toJSON    = genericToJSON    jsonOptions
@@ -179,10 +193,10 @@
 
 token :: (Happstack m) =>
          AcidState AuthenticateState
-      -> (UserId -> IO Bool)
+      -> AuthenticateConfig
       -> AcidState PasswordState
       -> m Response
-token authenticateState isAuthAdmin passwordState =
+token authenticateState authenticateConfig passwordState =
   do method POST
      (Just (Body body)) <- takeRequestBody =<< askRq
      case Aeson.decode body of
@@ -195,8 +209,8 @@
                 do valid <- query' passwordState (VerifyPasswordForUserId (u ^. userId) password)
                    if not valid
                      then unauthorized $ toJSONError InvalidUsernamePassword
-                     else do token <- addTokenCookie authenticateState isAuthAdmin u
-                             resp 201 $ toResponseBS "application/json" $ encode $ Object $ HashMap.fromList [("token", toJSON token)]
+                     else do token <- addTokenCookie authenticateState authenticateConfig u
+                             resp 201 $ toJSONSuccess (Object $ HashMap.fromList [("token", toJSON token)]) -- toResponseBS "application/json" $ encode $ Object $ HashMap.fromList [("token", toJSON token)]
 
 ------------------------------------------------------------------------------
 -- account
@@ -228,25 +242,47 @@
 account :: (Happstack m) =>
            AcidState AuthenticateState
         -> AcidState PasswordState
+        -> AuthenticateConfig
+        -> PasswordConfig
         -> Maybe (UserId, AccountURL)
         -> m (Either PasswordError UserId)
 -- handle new account creation via POST to /account
 -- FIXME: check that password and password confirmation match
-account authenticateState passwordState Nothing =
+account authenticateState passwordState authenticateConfig passwordConfig Nothing =
   do method POST
      (Just (Body body)) <- takeRequestBody =<< askRq
      case Aeson.decode body of
        Nothing               -> badRequest (Left $ CoreError JSONDecodeFailed)
        (Just newAccount) ->
-         do eUser <- update' authenticateState (CreateUser $ _naUser newAccount)
-            case eUser of
-              (Left e) -> return $ Left (CoreError e)
-              (Right user) -> do
-                hashed <- mkHashedPass (_naPassword newAccount)
-                update' passwordState (SetPassword (user ^. userId) hashed)
-                ok $ (Right (user ^. userId))
+           case (authenticateConfig ^. usernameAcceptable) (newAccount ^. naUser ^. username) of
+             (Just e) -> return $ Left (CoreError e)
+             Nothing ->
+                 case validEmail (authenticateConfig ^. requireEmail) (newAccount ^. naUser ^. email) of
+                   (Just e) -> return $ Left e
+                   Nothing ->
+                         if (newAccount ^. naPassword /= newAccount ^. naPasswordConfirm)
+                         then ok $ Left PasswordMismatch
+                         else case (passwordConfig ^. passwordAcceptable) (newAccount ^. naPassword) of
+                                (Just passwdError) -> ok $ Left (UnacceptablePassword passwdError)
+                                Nothing -> do
+                                  eUser <- update' authenticateState (CreateUser $ _naUser newAccount)
+                                  case eUser of
+                                    (Left e) -> return $ Left (CoreError e)
+                                    (Right user) -> do
+                                       hashed <- mkHashedPass (_naPassword newAccount)
+                                       update' passwordState (SetPassword (user ^. userId) hashed)
+                                       ok $ (Right (user ^. userId))
+    where
+      validEmail :: Bool -> Maybe Email -> Maybe PasswordError
+      validEmail required mEmail =
+          case (required, mEmail) of
+            (True, Nothing) -> Just $ CoreError InvalidEmail
+            (False, Just (Email "")) -> Nothing
+            (False, Nothing) -> Nothing
+            (_, Just email) -> if Email.isValid (Text.encodeUtf8 (email ^. unEmail)) then Nothing else Just $ CoreError InvalidEmail
+
 -- handle updates to /account/<userId>/*
-account authenticateState passwordState (Just (uid, url)) =
+account authenticateState passwordState authenticateConfig passwordConfig (Just (uid, url)) =
   case url of
     Password ->
       do method POST
@@ -268,9 +304,14 @@
                               do b <- verifyPassword authenticateState passwordState (token ^. tokenUser ^. username) (changePassword ^. cpOldPassword)
                                  if not b
                                    then forbidden (Left InvalidPassword)
-                                   else do pw <- mkHashedPass (changePassword ^. cpNewPassword)
-                                           update' passwordState (SetPassword uid pw)
-                                           ok $ (Right uid)
+                                   else if (changePassword ^. cpNewPassword /= changePassword ^. cpNewPasswordConfirm)
+                                        then ok $ (Left PasswordMismatch)
+                                        else case (passwordConfig ^. passwordAcceptable) (changePassword ^. cpNewPassword) of
+                                               (Just e) -> ok (Left $ UnacceptablePassword e)
+                                               Nothing -> do
+                                                   pw <- mkHashedPass (changePassword ^. cpNewPassword)
+                                                   update' passwordState (SetPassword uid pw)
+                                                   ok $ (Right uid)
 
 ------------------------------------------------------------------------------
 -- passwordReset
@@ -287,12 +328,11 @@
 
 -- | request reset password
 passwordRequestReset :: (Happstack m) =>
-                        Text
-                     -> Text
+                        PasswordConfig
                      -> AcidState AuthenticateState
                      -> AcidState PasswordState
                      -> m (Either PasswordError Text)
-passwordRequestReset resetLink domain authenticateState passwordState =
+passwordRequestReset passwordConfig authenticateState passwordState =
   do method POST
      (Just (Body body)) <- takeRequestBody =<< askRq
      case Aeson.decode body of
@@ -309,9 +349,9 @@
                        case eResetToken of
                          (Left err) -> return (Left err)
                          (Right resetToken) ->
-                           do let resetLink' = resetLink <> (Text.decodeUtf8 $ renderQuery True $ toQuery [("reset_token"::Text, resetToken)])
---                              liftIO $ Text.putStrLn resetLink' -- FIXME: don't print to stdout
-                              sendResetEmail toEm (Email ("no-rneplay@" <> domain)) resetLink'
+                           do let resetLink' = (passwordConfig ^. resetLink) <> (Text.decodeUtf8 $ renderQuery True $ toQuery [("reset_token"::Text, resetToken)])
+                              liftIO $ Text.putStrLn resetLink' -- FIXME: don't print to stdout
+                              sendResetEmail toEm (Email ("no-reply@" <> (passwordConfig ^. domain))) resetLink'
                               return (Right "password reset request email sent.") -- FIXME: I18N
 
 -- | issueResetToken
@@ -357,8 +397,9 @@
 passwordReset :: (Happstack m) =>
                  AcidState AuthenticateState
               -> AcidState PasswordState
-              -> m (Either PasswordError ())
-passwordReset authenticateState passwordState =
+              -> PasswordConfig
+              -> m (Either PasswordError Text)
+passwordReset authenticateState passwordState passwordConfig =
   do method POST
      (Just (Body body)) <- takeRequestBody =<< askRq
      case Aeson.decode body of
@@ -370,10 +411,11 @@
               (Just (user, _)) ->
                 if password /= passwordConfirm
                 then return (Left PasswordMismatch)
-                else do pw <-  mkHashedPass password
-                        update' passwordState (SetPassword (user ^. userId) pw)
-                        ok $ Right ()
-
+                else case (passwordConfig ^. passwordAcceptable) password of
+                       (Just e) -> ok $ Left $ UnacceptablePassword e
+                       Nothing -> do pw <-  mkHashedPass password
+                                     update' passwordState (SetPassword (user ^. userId) pw)
+                                     ok $ Right "Password Reset." -- I18N
          {-
          do mTokenTxt <- optional $ queryString $ lookText' "reset_btoken"
             case mTokenTxt of
diff --git a/Happstack/Authenticate/Password/Partials.hs b/Happstack/Authenticate/Password/Partials.hs
--- a/Happstack/Authenticate/Password/Partials.hs
+++ b/Happstack/Authenticate/Password/Partials.hs
@@ -71,7 +71,7 @@
     ChangePassword ->
       do mUser <- getToken authenticateState
          case mUser of
-           Nothing     -> [hsx| <p><% show NotAuthenticated %></p> |]
+           Nothing     -> unauthorized =<< [hsx| <p><% show NotAuthenticated %></p> |] -- FIXME: I18N
            (Just (token, _)) -> changePasswordForm (token ^. tokenUser ^. userId)
     RequestResetPasswordForm -> requestResetPasswordForm
     ResetPasswordForm -> resetPasswordForm
@@ -81,26 +81,26 @@
 signupPasswordForm =
      [hsx|
        <form ng-submit="signupPassword()" role="form">
+        <div>{{signup_error}}</div>
         <div class="form-group">
          <label class="sr-only" for="su-username"><% UsernameMsg %></label>
-         <input class="form-control" ng-model="signup.naUser.username" type="text" id="username" name="su-username" placeholder=UsernameMsg />
+         <input class="form-control" ng-model="signup.naUser.username" type="text" id="username" name="su-username" value="" placeholder=UsernameMsg />
         </div>
         <div class="form-group">
          <label class="sr-only" for="su-email"><% EmailMsg %></label>
-         <input class="form-control" ng-model="signup.naUser.email" type="email" id="su-email" name="email" placeholder=EmailMsg />
+         <input class="form-control" ng-model="signup.naUser.email" type="email" id="su-email" name="email" value="" placeholder=EmailMsg />
         </div>
         <div class="form-group">
          <label class="sr-only" for="su-password"><% PasswordMsg %></label>
-         <input class="form-control" ng-model="signup.naPassword" type="password" id="su-password" name="su-pass" placeholder=PasswordMsg />
+         <input class="form-control" ng-model="signup.naPassword" type="password" id="su-password" name="su-pass" value="" placeholder=PasswordMsg />
         </div>
         <div class="form-group">
          <label class="sr-only" for="su-password-confirm"><% PasswordConfirmationMsg %></label>
-         <input class="form-control" ng-model="signup.naPasswordConfirm" type="password" id="su-password-confirm" name="su-pass-confirm" placeholder=PasswordConfirmationMsg />
+         <input class="form-control" ng-model="signup.naPasswordConfirm" type="password" id="su-password-confirm" name="su-pass-confirm" value="" placeholder=PasswordConfirmationMsg />
         </div>
         <div class="form-group">
          <input class="form-control" type="submit" value=SignUpMsg />
         </div>
-        <div>{{signup_error}}</div>
        </form>
   |]
 
@@ -111,6 +111,7 @@
     <span>
      <span ng-show="!isAuthenticated">
       <form ng-submit="login()" role="form"  (if inline then ["class" := "navbar-form navbar-left"] :: [Attr Text Text] else [])>
+       <div class="form-group">{{username_password_error}}</div>
        <div class="form-group">
         <label class="sr-only" for="username"><% UsernameMsg %> </label>
         <input class="form-control" ng-model="user.user" type="text" id="username" name="user" placeholder=UsernameMsg />
@@ -123,7 +124,6 @@
        <input class="form-control" type="submit" value=SignInMsg />
        </div>
       </form>
-      <div>{{username_password_error}}</div>
      </span>
     </span>
   |]
@@ -132,7 +132,7 @@
 logoutForm = [hsx|
      <span ng-show="isAuthenticated">
       <div class="form-group">
-       <a class="navbar-right" ng-click="logout()" href=""><% LogoutMsg %></a>
+       <a ng-click="logout()" href="#"><% LogoutMsg %></a>
       </div>
      </span>
  |]
@@ -145,6 +145,7 @@
      let changePasswordFn = "changePassword('" <> url <> "')"
      [hsx|
        <form ng-submit=changePasswordFn role="form">
+        <div class="form-group">{{change_password_error}}</div>
         <div class="form-group">
          <label class="sr-only" for="password"><% OldPasswordMsg %></label>
          <input class="form-control" ng-model="password.cpOldPassword" type="password" id="old-password" name="old-pass" placeholder=OldPasswordMsg />
@@ -160,7 +161,6 @@
         <div class="form-group">
          <input class="form-control" type="submit" value=ChangePasswordMsg />
         </div>
-        <div>{{change_password_error}}</div>
        </form>
 
      |]
@@ -173,6 +173,7 @@
      [hsx|
       <div>
        <form ng-submit="requestResetPassword()" role="form">
+        <div class="form-group">{{request_reset_password_msg}}</div>
         <div class="form-group">
          <label class="sr-only" for="reset-username"><% UsernameMsg %></label>
          <input class="form-control" ng-model="requestReset.rrpUsername" type="text" id="reset-username" name="username" placeholder=UsernameMsg />
@@ -181,7 +182,6 @@
          <input class="form-control" type="submit" value=RequestPasswordResetMsg />
         </div>
        </form>
-       <div>{{request_reset_password_msg}}</div>
       </div>
      |]
 
@@ -191,6 +191,7 @@
   [hsx|
       <div>
        <form ng-submit="resetPassword()" role="form">
+        <div class="form-group">{{reset_password_msg}}</div>
         <div class="form-group">
          <label class="sr-only" for="reset-password"><% PasswordMsg %></label>
          <input class="form-control" ng-model="reset.rpPassword" type="password" id="reset-password" name="reset-password" placeholder=PasswordMsg />
@@ -203,6 +204,5 @@
          <input class="form-control" type="submit" value=ChangePasswordMsg />
         </div>
        </form>
-       <div>{{reset_password_msg}}</div>
       </div>
   |]
diff --git a/Happstack/Authenticate/Password/Route.hs b/Happstack/Authenticate/Password/Route.hs
--- a/Happstack/Authenticate/Password/Route.hs
+++ b/Happstack/Authenticate/Password/Route.hs
@@ -6,8 +6,8 @@
 import Data.Acid.Local       (createCheckpointAndClose, openLocalStateFrom)
 import Data.Text             (Text)
 import Data.UserId           (UserId)
-import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod, AuthenticateState, AuthenticateURL, CoreError(..), toJSONError, toJSONResponse)
-import Happstack.Authenticate.Password.Core (PasswordError(..), PasswordState, account, initialPasswordState, passwordReset, passwordRequestReset, token)
+import Happstack.Authenticate.Core (AuthenticationHandler, AuthenticationMethod, AuthenticateConfig(..), AuthenticateState, AuthenticateURL, CoreError(..), toJSONError, toJSONResponse)
+import Happstack.Authenticate.Password.Core (PasswordConfig(..), PasswordError(..), PasswordState, account, initialPasswordState, passwordReset, passwordRequestReset, token)
 import Happstack.Authenticate.Password.Controllers (usernamePasswordCtrl)
 import Happstack.Authenticate.Password.URL (PasswordURL(..), passwordAuthenticationMethod)
 import Happstack.Authenticate.Password.Partials (routePartial)
@@ -25,37 +25,35 @@
 ------------------------------------------------------------------------------
 
 routePassword :: (Happstack m) =>
-                 Text
-              -> Text
+                 PasswordConfig
               -> AcidState AuthenticateState
-              -> (UserId -> IO Bool)
+              -> AuthenticateConfig
               -> AcidState PasswordState
               -> [Text]
               -> RouteT AuthenticateURL (ReaderT [Lang] m) Response
-routePassword resetLink domain authenticateState isAuthAdmin passwordState pathSegments =
+routePassword passwordConfig authenticateState authenticateConfig passwordState pathSegments =
   case parseSegments fromPathSegments pathSegments of
     (Left _) -> notFound $ toJSONError URLDecodeFailed
     (Right url) ->
       case url of
-        Token        -> token authenticateState isAuthAdmin passwordState
-        Account mUrl -> toJSONResponse <$> account authenticateState passwordState mUrl
+        Token        -> token authenticateState authenticateConfig passwordState
+        Account mUrl -> toJSONResponse <$> account authenticateState passwordState authenticateConfig passwordConfig mUrl
         (Partial u)  -> do xml <- unXMLGenT (routePartial authenticateState u)
-                           ok $ toResponse (html4StrictFrag, xml)
-        PasswordRequestReset -> toJSONResponse <$> passwordRequestReset resetLink domain authenticateState passwordState
-        PasswordReset        -> toJSONResponse <$> passwordReset authenticateState passwordState
+                           return $ toResponse (html4StrictFrag, xml)
+        PasswordRequestReset -> toJSONResponse <$> passwordRequestReset passwordConfig authenticateState passwordState
+        PasswordReset        -> toJSONResponse <$> passwordReset authenticateState passwordState passwordConfig
         UsernamePasswordCtrl -> toResponse <$> usernamePasswordCtrl
 
 ------------------------------------------------------------------------------
 -- initPassword
 ------------------------------------------------------------------------------
 
-initPassword :: Text
-             -> Text
+initPassword :: PasswordConfig
              -> FilePath
              -> AcidState AuthenticateState
-             -> (UserId -> IO Bool)
+             -> AuthenticateConfig
              -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)
-initPassword resetLink domain basePath authenticateState isAuthAdmin =
+initPassword passwordConfig basePath authenticateState authenticateConfig =
   do passwordState <- openLocalStateFrom (combine basePath "password") initialPasswordState
      let shutdown = \normal ->
            if normal
@@ -65,5 +63,5 @@
            do langsOveride <- queryString $ lookTexts' "_LANG"
               langs        <- bestLanguage <$> acceptLanguage
               mapRouteT (flip runReaderT (langsOveride ++ langs)) $
-               routePassword resetLink domain authenticateState isAuthAdmin passwordState pathSegments
+               routePassword passwordConfig authenticateState authenticateConfig passwordState pathSegments
      return (shutdown, (passwordAuthenticationMethod, authenticationHandler), usernamePasswordCtrl)
diff --git a/Happstack/Authenticate/Route.hs b/Happstack/Authenticate/Route.hs
--- a/Happstack/Authenticate/Route.hs
+++ b/Happstack/Authenticate/Route.hs
@@ -13,7 +13,7 @@
 import Data.UserId (UserId)
 import HSP.JMacro (IntegerSupply(..))
 import Happstack.Authenticate.Controller (authenticateCtrl)
-import Happstack.Authenticate.Core (AuthenticateState, AuthenticateURL(..), AuthenticationHandler, AuthenticationHandlers, AuthenticationMethod, CoreError(HandlerNotFound), initialAuthenticateState, toJSONError)
+import Happstack.Authenticate.Core (AuthenticateConfig, AuthenticateState, AuthenticateURL(..), AuthenticationHandler, AuthenticationHandlers, AuthenticationMethod, CoreError(HandlerNotFound), initialAuthenticateState, toJSONError)
 import Happstack.Server (notFound, ok, Response, ServerPartT, ToMessage(toResponse))
 import Happstack.Server.JMacro ()
 import Language.Javascript.JMacro (JStat)
@@ -46,14 +46,14 @@
 
 initAuthentication
   :: Maybe FilePath
-  -> (UserId -> IO Bool)
-  -> [FilePath -> AcidState AuthenticateState -> (UserId -> IO Bool) -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)]
+  -> AuthenticateConfig
+  -> [FilePath -> AcidState AuthenticateState -> AuthenticateConfig -> IO (Bool -> IO (), (AuthenticationMethod, AuthenticationHandler), RouteT AuthenticateURL (ServerPartT IO) JStat)]
   -> IO (IO (), AuthenticateURL -> RouteT AuthenticateURL (ServerPartT IO) Response, AcidState AuthenticateState)
-initAuthentication mBasePath isAuthAdmin initMethods =
+initAuthentication mBasePath authenticateConfig initMethods =
   do let authenticatePath = combine (fromMaybe "state" mBasePath) "authenticate"
      authenticateState <- openLocalStateFrom (combine authenticatePath "core") initialAuthenticateState
      -- FIXME: need to deal with one of the initMethods throwing an exception
-     (cleanupPartial, handlers, javascript) <- unzip3 <$> mapM (\initMethod -> initMethod authenticatePath authenticateState isAuthAdmin) initMethods
+     (cleanupPartial, handlers, javascript) <- unzip3 <$> mapM (\initMethod -> initMethod authenticatePath authenticateState authenticateConfig) initMethods
      let cleanup = sequence_ $ createCheckpointAndClose authenticateState : (map (\c -> c True) cleanupPartial)
          h       = route javascript (Map.fromList handlers)
      return (cleanup, h, authenticateState)
diff --git a/happstack-authenticate.cabal b/happstack-authenticate.cabal
--- a/happstack-authenticate.cabal
+++ b/happstack-authenticate.cabal
@@ -1,5 +1,5 @@
 Name:                happstack-authenticate
-Version:             2.2.0
+Version:             2.3.0
 Synopsis:            Happstack Authentication Library
 Description:         A themeable authentication library with support for username+password and OpenId.
 Homepage:            http://www.happstack.com/
@@ -49,6 +49,7 @@
                        bytestring                   >= 0.9  && < 0.11,
                        containers                   >= 0.4  && < 0.6,
                        data-default                 >= 0.5  && < 0.6,
+                       email-validate               >= 2.1  && < 2.2,
                        filepath                     >= 1.3  && < 1.5,
                        hsx2hs                       >= 0.13 && < 0.14,
                        jmacro                       >= 0.6.11  && < 0.7,
diff --git a/messages/core/en.msg b/messages/core/en.msg
--- a/messages/core/en.msg
+++ b/messages/core/en.msg
@@ -5,5 +5,8 @@
 Forbidden: Forbidden.
 JSONDecodeFailed: Failed to decode JSON data.
 InvalidUserId: Invalid UserId
+UsernameNotAcceptable: Username not acceptable.
+InvalidEmail: Invalid email address.
+
 
 
diff --git a/messages/password/error/en.msg b/messages/password/error/en.msg
--- a/messages/password/error/en.msg
+++ b/messages/password/error/en.msg
@@ -7,5 +7,5 @@
 MissingResetToken: Missing reset token
 InvalidResetToken: Invalid reset token
 PasswordMismatch: Passwords do not match
+UnacceptablePassword msg@Text: Unacceptable Password. #{msg}
 CoreError e@CoreError: #{renderMessage HappstackAuthenticateI18N ["en"] e}
-
