diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css
index bc9647ede18c9b98973c77c64c22679a82fc97a0..195460e49c2f317d35be5151f7a54f86a4abb58b 100644
--- a/assets/default/css/theme.css
+++ b/assets/default/css/theme.css
@@ -626,11 +626,13 @@ label.required {
   text-align: left;
 }
 #clientLogin {
-  width: 400px;
+    display: block;
   margin-top: 20px;
-  padding: 10px 100px 10px 10px;
+  padding: 20px;
   border: 1px solid #ccc;
-  background: url('../images/lock.png?1319655200') 440px 50% no-repeat #f6f6f6;
+  border-radius: 5px;
+  box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
+  background: url('../images/lock.png?1319655200') 95% 50% no-repeat #f6f6f6;
 }
 #clientLogin p {
   clear: both;
diff --git a/client.inc.php b/client.inc.php
index ee9a9fcae443df2d4c0ade4636270d102b9955e8..ad4c7ee4e18c12841a46ac239fd0a7b90bd3e597 100644
--- a/client.inc.php
+++ b/client.inc.php
@@ -56,7 +56,7 @@ if($thisclient && $thisclient->getId() && $thisclient->isValid()){
 /******* CSRF Protectin *************/
 // Enforce CSRF protection for POSTS
 if ($_POST  && !$ost->checkCSRFToken()) {
-    @header('Location: index.php');
+    Http::redirect('index.php');
     //just incase redirect fails
     die('Action denied (400)!');
 }
@@ -68,4 +68,13 @@ $ost->addExtraHeader('<meta name="csrf_token" content="'.$ost->getCSRFToken().'"
 define('PAGE_LIMIT', DEFAULT_PAGE_LIMIT);
 
 $nav = new UserNav($thisclient, 'home');
+
+$exempt = in_array(basename($_SERVER['SCRIPT_NAME']), array('logout.php', 'ajax.php', 'logs.php', 'upgrade.php'));
+
+if (!$exempt && $thisclient && ($acct = $thisclient->getAccount())
+        && $acct->isPasswdResetForced()) {
+    $warn = 'Password change required to continue';
+    require('profile.php'); //profile.php must request this file as require_once to avoid problems.
+    exit;
+}
 ?>
diff --git a/include/class.auth.php b/include/class.auth.php
index c7b80da05b9ce669359702cbfccc14bd97ffc1d5..74a4664b9165686b7705bc9bf4de73978fb93fe3 100644
--- a/include/class.auth.php
+++ b/include/class.auth.php
@@ -461,7 +461,6 @@ abstract class UserAuthenticationBackend  extends AuthenticationBackend {
         list($id, $auth) = explode(':', $_SESSION['_auth']['user']['key']);
 
         if (!($bk=static::getBackend($id)) //get the backend
-                || !$bk->supportsAuthentication() //Make sure it can authenticate
                 || !($user=$bk->validate($auth)) //Get AuthicatedUser
                 || !($user instanceof AuthenticatedUser) // Make sure it user
                 || $user->getId() != $_SESSION['_auth']['user']['id'] // check ID
@@ -800,12 +799,7 @@ class osTicketClientAuthentication extends UserAuthenticationBackend {
     static $id = "client";
 
     function authenticate($username, $password) {
-        if (strpos($username, '@') !== false)
-            $user = User::lookup(array('emails__address'=>$username));
-        else
-            $user = User::lookup(array('account__username'=>$username));
-
-        if (!$user)
+        if (!($user = self::_identify($authkey)))
             return;
 
         if (($client = new ClientSession(new EndUser($user)))
@@ -818,18 +812,68 @@ class osTicketClientAuthentication extends UserAuthenticationBackend {
     }
 
     protected function validate($authkey) {
+        if (!($user = self::_identify($authkey)))
+            return;
+
+        if (($client = new ClientSession(new EndUser($user))) && $client->getId())
+            return $client;
+    }
+
+    protected function _identify($username) {
         if (strpos($authkey, '@') !== false)
             $user = User::lookup(array('emails__address'=>$authkey));
         else
-            $user = User::lookup(array('account__authkey'=>$authkey));
+            $user = User::lookup(array('account__username'=>$authkey));
 
-        if (!$user)
-            return;
+        return $user;
+    }
 
-        if (($client = new ClientSession(new EndUser($user))) && $client->getId())
+}
+UserAuthenticationBackend::register('osTicketClientAuthentication');
+
+class ClientPasswordResetTokenBackend extends UserAuthenticationBackend {
+    static $id = "pwreset.client";
+
+    function supportsAuthentication() {
+        return false;
+    }
+
+    function signOn($errors=array()) {
+        global $ost;
+
+        if (!isset($_POST['userid']) || !isset($_POST['token']))
+            return false;
+        elseif (!($_config = new Config('pwreset')))
+            return false;
+        elseif (!($acct = ClientAccount::lookupByUsername($_POST['userid']))
+                || !$acct->getId()
+                || !($client = new ClientSession(new EndUser($acct->getUser()))))
+            $errors['msg'] = 'Invalid user-id given';
+        elseif (!($id = $_config->get($_POST['token']))
+                || $id != $client->getId())
+            $errors['msg'] = 'Invalid reset token';
+        elseif (!($ts = $_config->lastModified($_POST['token']))
+                && ($ost->getConfig()->getPwResetWindow() < (time() - strtotime($ts))))
+            $errors['msg'] = 'Invalid reset token';
+        elseif (!$acct->forcePasswdReset())
+            $errors['msg'] = 'Unable to reset password';
+        else
             return $client;
     }
 
+    function login($client, $bk) {
+        $_SESSION['_client']['reset-token'] = $_POST['token'];
+        Signal::send('auth.pwreset.login', $client);
+        return parent::login($client, $bk);
+    }
+
+    protected function validate($authkey) {
+        if (!($acct = ClientAccount::lookupByUsername($authkey)))
+            return;
+
+        if (($client = new ClientSession(new EndUser($acct->getUser()))) && $client->getId())
+            return $client;
+    }
 }
-UserAuthenticationBackend::register('osTicketClientAuthentication');
+UserAuthenticationBackend::register('ClientPasswordResetTokenBackend');
 ?>
diff --git a/include/class.client.php b/include/class.client.php
index 309cb78d2f4777b177f414f336e40a0c3c6e5c45..8e5deee12c024e840ebbe87764ece8d4be477770 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -259,7 +259,7 @@ require_once INCLUDE_DIR.'class.orm.php';
 class ClientAccountModel extends VerySimpleModel {
     static $meta = array(
         'table' => USER_ACCOUNT_TABLE,
-        'pk' => 'id',
+        'pk' => array('id'),
         'joins' => array(
             'user' => array(
                 'null' => false,
@@ -273,8 +273,9 @@ class ClientAccount extends ClientAccountModel {
     var $_options = null;
     var $timezone;
 
-    const LOCKED = 0x0001;
-    const PASSWD_RESET_REQUIRED = 0x0002;
+    const CONFIRMED             = 0x0001;
+    const LOCKED                = 0x0002;
+    const PASSWD_RESET_REQUIRED = 0x0004;
 
     function __onload() {
         if ($this->get('timezone_id')) {
@@ -284,6 +285,14 @@ class ClientAccount extends ClientAccountModel {
         }
     }
 
+    function getId() {
+        return $this->get('id');
+    }
+
+    function getUserId() {
+        return $this->get('user_id');
+    }
+
     function checkPassword($password, $autoupdate=true) {
 
         /*bcrypt based password match*/
@@ -308,17 +317,73 @@ class ClientAccount extends ClientAccountModel {
         return $this->checkPassword($password, false);
     }
 
+    function hasPassword() {
+        return (bool) $this->get('passwd');
+    }
+
+    function sendResetEmail() {
+        global $ost, $cfg;
+
+        $tpl= $ost->getConfig()->getDefaultTemplate();
+
+        $token = Misc::randCode(48); // 290-bits
+        if (!($template = $tpl->getMsgTemplate('staff.pwreset')))
+            return new Error('Unable to retrieve password reset email template');
+
+        $vars = array(
+            'url' => $ost->getConfig()->getBaseUrl(),
+            'token' => $token,
+            'client' => $this,
+            'reset_link' => sprintf(
+                "%s/pwreset.php?token=%s",
+                $ost->getConfig()->getBaseUrl(),
+                $token),
+        );
+
+        if(!($email=$cfg->getAlertEmail()))
+            $email = $cfg->getDefaultEmail();
+
+        $info = array('email' => $email, 'vars' => &$vars, 'log'=>true);
+        Signal::send('auth.pwreset.email', $this, $info);
+
+        $msg = $ost->replaceTemplateVariables($template->asArray(), $vars);
+
+        $_config = new Config('pwreset');
+        $_config->set($vars['token'], $this->user->getId());
+
+        $email->send($this->user->default_email->get('address'),
+            $msg['subj'], $msg['body']);
+    }
+
     function forcePasswdReset() {
-        $this->set('status', $this->get('status') | self::PASSWD_RESET_REQUIRED);
-        $this->save();
+        $this->_setStatus(self::PASSWD_RESET_REQUIRED);
+        return $this->save();
+    }
+
+    function _getStatus($flag) {
+        return 0 !== ($this->get('status') & $flag);
+    }
+
+    function _clearStatus($flag) {
+        return $this->set('status', $this->get('status') & ~$flag);
+    }
+
+    function _setStatus($flag) {
+        return $this->set('status', $this->get('status') | $flag);
+    }
+
+    function isPasswdResetForced() {
+        return $this->_getStatus(self::PASSWD_RESET_REQUIRED);
     }
 
     function cancelResetTokens() {
         // TODO: Drop password-reset tokens from the config table for
         //       this user id
         $sql = 'DELETE FROM '.CONFIG_TABLE.' WHERE `namespace`="pwreset"
-            AND `value`='.db_input($this->getId());
-        db_query($sql, false);
+            AND `key`='.db_input($this->getUserId());
+        if (!db_query($sql, false))
+            return false;
+
         unset($_SESSION['_client']['reset-token']);
     }
 
@@ -328,19 +393,26 @@ class ClientAccount extends ClientAccountModel {
         return $base;
     }
 
+    function getUser() {
+        $user = User::lookup($this->get('user_id'));
+        $user->set('account', $this);
+        return $user;
+    }
+
     function update($vars, &$errors) {
-        if($vars['passwd1'] || $vars['passwd2'] || $vars['cpasswd']) {
+        $rtoken = $_SESSION['_client']['reset-token'];
+        if ($vars['passwd1'] || $vars['passwd2'] || $vars['cpasswd'] || $rtoken) {
 
-            if(!$vars['passwd1'])
+            if (!$vars['passwd1'])
                 $errors['passwd1']='New password required';
-            elseif($vars['passwd1'] && strlen($vars['passwd1'])<6)
+            elseif ($vars['passwd1'] && strlen($vars['passwd1'])<6)
                 $errors['passwd1']='Must be at least 6 characters';
-            elseif($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2']))
+            elseif ($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2']))
                 $errors['passwd2']='Password(s) do not match';
 
-            if (($rtoken = $_SESSION['_client']['reset-token'])) {
+            if ($rtoken) {
                 $_config = new Config('pwreset');
-                if ($_config->get($rtoken) != $this->getId())
+                if ($_config->get($rtoken) != $this->getUserId())
                     $errors['err'] =
                         'Invalid reset token. Logout and try again';
                 elseif (!($ts = $_config->lastModified($rtoken))
@@ -348,31 +420,42 @@ class ClientAccount extends ClientAccountModel {
                     $errors['err'] =
                         'Invalid reset token. Logout and try again';
             }
-            elseif(!$vars['cpasswd'])
+            elseif (!$vars['cpasswd'])
                 $errors['cpasswd']='Current password required';
-            elseif(!$this->hasCurrentPassword($vars['cpasswd']))
+            elseif (!$this->hasCurrentPassword($vars['cpasswd']))
                 $errors['cpasswd']='Invalid current password!';
-            elseif(!strcasecmp($vars['passwd1'], $vars['cpasswd']))
+            elseif (!strcasecmp($vars['passwd1'], $vars['cpasswd']))
                 $errors['passwd1']='New password MUST be different from the current password!';
         }
 
-        if(!$vars['timezone_id'])
+        if (!$vars['timezone_id'])
             $errors['timezone_id']='Time zone required';
 
-        if($errors) return false;
+        if ($errors) return false;
 
         $this->set('timezone_id', $vars['timezone_id']);
-        $this->set('dst',isset($vars['dst'])?1:0);
+        $this->set('dst', isset($vars['dst']) ? 1 : 0);
 
         if ($vars['passwd1']) {
             $this->set('passwd', Passwd::hash($vars['passwd1']));
             $info = array('password' => $vars['passwd1']);
             Signal::send('auth.pwchange', $this, $info);
             $this->cancelResetTokens();
+            $this->_clearStatus(self::PASSWD_RESET_REQUIRED);
         }
 
         return $this->save();
     }
+
+    static function lookupByUsername($username) {
+        if (strpos($username, '@') !== false)
+            $user = self::lookup(array('user__emails__address'=>$username));
+        else
+            $user = self::lookup(array('username'=>$username));
+
+        return $user;
+    }
 }
+ClientAccount::_inspect();
 
 ?>
diff --git a/include/class.user.php b/include/class.user.php
index c5b0f5b9e47b67bbf8027cd8af15bc583ea4c7da..c465f095fc3acbf684f888d4578159e80f95272b 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -38,7 +38,7 @@ class UserModel extends VerySimpleModel {
             ),
             'account' => array(
                 'list' => false,
-                'reverse' => 'ClientAccountModel.user',
+                'reverse' => 'ClientAccount.user',
             ),
             'default_email' => array(
                 'null' => true,
@@ -147,6 +147,10 @@ class User extends UserModel {
         return $this->created;
     }
 
+    function getAccount() {
+        return $this->account;
+    }
+
     function to_json() {
 
         $info = array(
diff --git a/include/client/pwreset.login.php b/include/client/pwreset.login.php
new file mode 100644
index 0000000000000000000000000000000000000000..a1c1ed4e75f656b8372d95052aa1201ba2e28545
--- /dev/null
+++ b/include/client/pwreset.login.php
@@ -0,0 +1,26 @@
+<?php
+if(!defined('OSTCLIENTINC')) die('Access Denied');
+
+$userid=Format::input($_POST['userid']);
+?>
+<h1>Forgot My Password</h1>
+<p>
+Enter your username or email address again in the form below and press the
+<strong>Login</strong> to access your account and reset your password.
+
+<form action="pwreset.php" method="post" id="clientLogin">
+    <div style="width:50%;display:inline-block">
+    <?php csrf_token(); ?>
+    <input type="hidden" name="do" value="reset"/>
+    <input type="hidden" name="token" value="<?php echo $_REQUEST['token']; ?>"/>
+    <strong><?php echo Format::htmlchars($banner); ?></strong>
+    <br>
+    <div>
+        <label for="username">Username:</label>
+        <input id="username" type="text" name="userid" size="30" value="<?php echo $userid; ?>">
+    </div>
+    <p>
+        <input class="btn" type="submit" value="Login">
+    </p>
+    </div>
+</form>
diff --git a/include/client/pwreset.request.php b/include/client/pwreset.request.php
new file mode 100644
index 0000000000000000000000000000000000000000..28922c8d2a9d6028ab18ec533ea7be9873c847ec
--- /dev/null
+++ b/include/client/pwreset.request.php
@@ -0,0 +1,26 @@
+<?php
+if(!defined('OSTCLIENTINC')) die('Access Denied');
+
+$userid=Format::input($_POST['userid']);
+?>
+<h1>Forgot My Password</h1>
+<p>
+Enter your username or email address in the form below and press the
+<strong>Send Email</strong> button to have a password reset link sent to
+your email account on file.
+
+<form action="pwreset.php" method="post" id="clientLogin">
+    <div style="width:50%;display:inline-block">
+    <?php csrf_token(); ?>
+    <input type="hidden" name="do" value="sendmail"/>
+    <strong><?php echo Format::htmlchars($banner); ?></strong>
+    <br>
+    <div>
+        <label for="username">Username:</label>
+        <input id="username" type="text" name="userid" size="30" value="<?php echo $userid; ?>">
+    </div>
+    <p>
+        <input class="btn" type="submit" value="Send Email">
+    </p>
+    </div>
+</form>
diff --git a/include/client/pwreset.sent.php b/include/client/pwreset.sent.php
new file mode 100644
index 0000000000000000000000000000000000000000..c76f50957ecc56ec8a2946a26d729e364d332968
--- /dev/null
+++ b/include/client/pwreset.sent.php
@@ -0,0 +1,13 @@
+<h1>Forgot My Password</h1>
+<p>
+Enter your username or email address in the form below and press the
+<strong>Send Email</strong> button to have a password reset link sent to
+your email account on file.
+
+<form action="pwreset.php" method="post" id="clientLogin">
+    <div style="width:50%;display:inline-block">
+    We have sent you a reset email to the email address you have on file for
+    your account. If you do not receive the email or cannot reset your
+    password, please ...
+    </div>
+</form>
diff --git a/pwreset.php b/pwreset.php
new file mode 100644
index 0000000000000000000000000000000000000000..615cbbee95b12566da0311abda30f4a92cdfc61a
--- /dev/null
+++ b/pwreset.php
@@ -0,0 +1,68 @@
+<?php
+
+require_once('client.inc.php');
+if(!defined('INCLUDE_DIR')) die('Fatal Error');
+define('CLIENTINC_DIR',INCLUDE_DIR.'client/');
+define('OSTCLIENTINC',TRUE); //make includes happy
+
+require_once(INCLUDE_DIR.'class.client.php');
+
+$inc = 'pwreset.request.php';
+if($_POST) {
+    if (!$ost->checkCSRFToken()) {
+        Http::response(400, 'Valid CSRF Token Required');
+        exit;
+    }
+    switch ($_POST['do']) {
+        case 'sendmail':
+            if (($acct=ClientAccount::lookupByUsername($_POST['userid']))) {
+                if (!$acct->hasPassword()) {
+                    $banner = 'Unable to reset password. Contact your administrator';
+                }
+                elseif (!$acct->sendResetEmail()) {
+                    $inc = 'pwreset.sent.php';
+                }
+            }
+            else
+                $banner = 'Unable to verify username '
+                    .Format::htmlchars($_POST['userid']);
+            break;
+        case 'create_account':
+            break;
+        case 'reset':
+            $inc = 'pwreset.login.php';
+            $errors = array();
+            if ($client = UserAuthenticationBackend::processSignOn($errors)) {
+                Http::redirect('index.php');
+            }
+            elseif (isset($errors['msg'])) {
+                $banner = $errors['msg'];
+            }
+            break;
+    }
+}
+elseif ($_GET['token']) {
+    $banner = 'Re-enter your username or email';
+    $_config = new Config('pwreset');
+    if (($id = $_config->get($_GET['token']))
+            && ($acct = ClientAccount::lookup(array('user_id'=>$id))))
+        $inc = 'pwreset.login.php';
+    elseif ($id && ($user = User::lookup($id)))
+        $inc = 'pwreset.create.php';
+    else
+        Http::redirect('index.php');
+}
+elseif ($cfg->allowPasswordReset()) {
+    $banner = 'Enter your username or email address below';
+}
+else {
+    $_SESSION['_staff']['auth']['msg']='Password resets are disabled';
+    return header('Location: index.php');
+}
+
+$nav = new UserNav();
+$nav->setActiveNav('status');
+require CLIENTINC_DIR.'header.inc.php';
+require CLIENTINC_DIR.$inc;
+require CLIENTINC_DIR.'footer.inc.php';
+?>