diff --git a/README.md b/README.md
index f168536..b14bd56 100644
--- a/README.md
+++ b/README.md
@@ -34,4 +34,8 @@ instructions, for example using drush:
 
 Implement hook_password_strength_minimum_score_alter(&$score, $account){} to
 override the global password_strength_default_required_score variable for a user
-account.
\ No newline at end of file
+account.
+
+Implement hook_password_strength_password_change_paths_alter(&$paths){} to
+override which paths are accessible when a user is forced to change their
+password after a minimum strength requirement elevation.
diff --git a/password_strength.install b/password_strength.install
index 0cbd2f6..9737f82 100644
--- a/password_strength.install
+++ b/password_strength.install
@@ -37,4 +37,44 @@ function password_strength_requirements($phase) {
 function password_strength_uninstall() {
   variable_del('password_strength_default_required_score');
   variable_del('password_strength_default_password_length');
-}
\ No newline at end of file
+}
+
+/**
+ * Implements hook_schema().
+ */
+function password_strength_schema() {
+  $schema['password_strength_score'] = array(
+    'description' => 'Stores the password strength score of each user.',
+    'fields' => array(
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'The {users}.uid this score belongs to.',
+      ),
+      'score' => array(
+        'type' => 'int',
+        'default' => 0,
+        'description' => 'The password strength score for this user.',
+      ),
+      'timestamp' => array(
+        'type' => 'int',
+        'default' => 0,
+        'description' => 'The timestamp when the password was set.',
+      ),
+    ),
+    'indexes' => array(
+      'uid' => array('uid', 'score'),
+    ),
+    'primary key' => array('uid'),
+  );
+
+  return $schema;
+}
+
+/**
+ * Install password_strength DB schema.
+ */
+function password_strength_update_7000() {
+  drupal_install_schema('password_strength');
+}
diff --git a/password_strength.module b/password_strength.module
index 1a2fe5a..e24832d 100644
--- a/password_strength.module
+++ b/password_strength.module
@@ -12,6 +12,31 @@ define('PASSWORD_STRENGTH_SCORE_STRONG', 3);
 define('PASSWORD_STRENGTH_SCORE_VERYSTRONG', 4);
 
 /**
+ * Implements hook_init().
+ */
+function password_strength_init() {
+  $change_password_path = 'user/' . $GLOBALS['user']->uid . '/edit';
+
+  // Display a reminder that the password needs to be changed if the user just
+  // logged in. Don't run drupal_set_message() on POST requests to avoid
+  // displaying the message after a successful password change.
+  $allowed_paths = array(
+    $change_password_path,
+    'logout',
+    'user/logout',
+  );
+
+  // Some sites might have other paths defined for changing the password.
+  drupal_alter('password_strength_password_change_paths', $allowed_paths);
+
+  if (!empty($_SESSION['password_strength_force_password_reset']) && !in_array(current_path(), $allowed_paths) && empty($_POST)) {
+    drupal_set_message(t('Your password does not meet the minimum complexity requirement. You must change your password to proceed on the site.'), 'error', FALSE);
+    $options = array('query' => drupal_get_destination());
+    drupal_goto($change_password_path, $options);
+  }
+}
+
+/**
  * Implements hook_xautoload().
  */
 function password_strength_xautoload($api) {
@@ -63,6 +88,12 @@ function password_strength_check_strength($account = NULL) {
  */
 function password_strength_form_alter(&$form, &$form_state, $form_id) {
   switch ($form_id) {
+    case 'user_login_block':
+    case 'user_login':
+      // Run user_login_submit handler after others.
+      $form['#submit'] = array_merge($form['#submit'], array('password_strength_form_user_login_submit'));
+      break;
+
     case 'user_register_form':
       // Include password strength JS check and validation handler.
       if (array_key_exists('pass', $form['account']) && password_strength_check_strength()) {
@@ -184,9 +215,34 @@ function password_strength_form_password_submit($form, &$form_state) {
     return;
   }
   list($account, $strength) = _password_strength_form_helper($form, $form_state);
+  // We need to store the password strength before the raw password gets hashed
+  // by user_save() in user_register_submit(). This ensures that the password
+  // strength is available in hook_user_insert().
+  $form_state['values']['password_strength_score'] = $strength;
   // Password has passed validation. Save strength and report change.
-  // Invoke password_strength_change hook.
-  module_invoke_all('password_strength_change', $account, $strength);
+  // Invoke password_strength_change hook if the account is fully-fledged. In
+  // the case of a new user, the uid is not yet available and this hook will be
+  // invoked in password_strength_user_insert() instead.
+  if ($account->uid > 0) {
+    module_invoke_all('password_strength_change', $account, $strength);
+  }
+}
+
+/**
+ * Submission handler for user login form.
+ */
+function password_strength_form_user_login_submit($form, &$form_state) {
+  list($account, $strength) = _password_strength_form_helper($form, $form_state);
+
+  // Set password strength for this user account upon successful login.
+  if (password_strength_get_user_score($account->uid) != $strength['score']) {
+    password_strength_set_user_score($account->uid, $strength['score']);
+  }
+
+  // Check password strength score and force a password change if needed.
+  if ((int) password_strength_get_user_score($account->uid) < password_strength_required_score($account)) {
+    $_SESSION['password_strength_force_password_reset'] = TRUE;
+  }
 }
 
 /**
@@ -593,3 +649,79 @@ function password_strength_required_score($account) {
 
   return $score;
 }
+
+/**
+ * Gets the password strength score of a given user.
+ *
+ * @param int $uid
+ *   User account id.
+ *
+ * @return int|null
+ *   The user password score, or null if the score is unknown.
+ */
+function password_strength_get_user_score($uid) {
+  $score = db_query('SELECT score from {password_strength_score} WHERE uid = :uid', array(':uid' => $uid))->fetchField();
+
+  return $score !== FALSE ? (int) $score : NULL;
+}
+
+/**
+ * Sets the password strength score of a given user.
+ *
+ * @param int $uid
+ *   User account id.
+ * @param int $score
+ *   Password score for this user account.
+ */
+function password_strength_set_user_score($uid, $score) {
+  db_merge('password_strength_score')
+    ->key(array(
+      'uid' => $uid,
+    ))
+    ->fields(array('score' => $score, 'timestamp' => REQUEST_TIME))
+    ->execute();
+}
+
+/**
+ * Deletes the password strength score of a given user.
+ *
+ * @param int $uid
+ *   User account id.
+ */
+function password_strength_delete_user_score($uid) {
+  db_delete('password_strength_score')
+    ->condition('uid', $uid)
+    ->execute();
+}
+
+/**
+ * Implements hook_password_strength_change().
+ */
+function password_strength_password_strength_change($account, $strength) {
+  // Register new password score for this user account.
+  password_strength_set_user_score($account->uid, $strength['score']);
+
+  // If it's a new registration, $_SESSION is not available.
+  if (isset($_SESSION)) {
+    unset($_SESSION['password_strength_force_password_reset']);
+  }
+}
+
+/**
+ * Implements hook_user_insert().
+ */
+function password_strength_user_insert(&$edit, $account, $category) {
+  if (!empty($edit['password_strength_score']) && $account->uid > 0) {
+    // New users do not yet have an uid during the validation and submission
+    // steps, so we have to invoke hook_password_strength_change() here.
+    module_invoke_all('password_strength_change', $account, $edit['password_strength_score']);
+  }
+}
+
+/**
+ * Implements hook_user_delete().
+ */
+function password_strength_user_delete($account) {
+  // Clean up password strength score for this deleted user.
+  password_strength_delete_user_score($account->uid);
+}
diff --git a/tests/password_strength.test b/tests/password_strength.test
index 36a9759..d3ca1c9 100644
--- a/tests/password_strength.test
+++ b/tests/password_strength.test
@@ -32,7 +32,7 @@ class PasswordStrengthTestCase extends DrupalWebTestCase {
   function setUp() {
     parent::setUp('password_strength', 'composer_manager');
     $this->web_user = $this->drupalCreateUser();
-    $this->admin_user = $this->drupalCreateUser(array('administer site configuration'));
+    $this->admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer users'));
   }
 
   /**
@@ -42,13 +42,81 @@ class PasswordStrengthTestCase extends DrupalWebTestCase {
     // Set required score to very weak and test that any password works.
     $this->setRequiredScore('0');
     $this->changePassword($this->web_user, 'Password1');
+    $this->assertTrue(password_strength_get_user_score($this->web_user->uid) === 0, t('User password score set.'));
     $this->changePassword($this->web_user, '35qzYI^HUbAZ');
+    $this->assertTrue(password_strength_get_user_score($this->web_user->uid) === 4, t('User password score set.'));
 
     // Set required score to very strong and test that a weak password fails and
     // that a strong password works.
     $this->setRequiredScore('4');
     $this->changePassword($this->web_user, 'Password1', TRUE);
+    $this->assertTrue(password_strength_get_user_score($this->web_user->uid) === 4, t('User password score remains the same.'));
     $this->changePassword($this->web_user, '35qzYI^HUbAZ');
+    $this->assertTrue(password_strength_get_user_score($this->web_user->uid) === 4, t('User password score remains the same.'));
+
+  }
+
+  /**
+   * Tests force password change on login.
+   */
+  public function testForcePasswordChange() {
+    // Verify that a password change is not forced on login if no strength
+    // requirement is set, or if the user password is equal to or stronger than
+    // the required strength.
+    foreach (array('0', '1', '2') as $minimum_strength) {
+      $this->setRequiredScore('0');
+      // Set password to strength 2 (good) password.
+      $this->changePassword($this->web_user, 'demodemo12');
+      $this->setRequiredScore($minimum_strength);
+      $this->drupalLogin($this->web_user);
+      $this->assertNoText(t("Your password does not meet the minimum complexity requirement. You must change your password to proceed on the site."), 'Force password change message is not displayed');
+      // Test that the user can browse the site.
+      $this->drupalGet('node');
+      $this->assertEqual($this->getUrl(), url('node', array('absolute' => TRUE)), 'The user was not redirected to the password form');
+      $this->drupalLogout();
+    }
+
+    // Verify that a password change is forced on login if the required strength
+    // is higher than the user password.
+    $this->setRequiredScore('0');
+    $this->changePassword($this->web_user, 'Password1');
+    $this->setRequiredScore('4');
+    $this->drupalLogin($this->web_user);
+    $this->assertEqual($this->getUrl(), url("user/{$this->web_user->uid}/edit", array('query' => array('destination' => "user/{$this->web_user->uid}"), 'absolute' => TRUE)), 'The user was redirected to the password form');
+    $this->assertText(t("Your password does not meet the minimum complexity requirement. You must change your password to proceed on the site."), 'Force password change message is displayed');
+
+    // Test that the user cannot browse the site.
+    $this->drupalGet('node');
+    $this->assertEqual($this->getUrl(), url("user/{$this->web_user->uid}/edit", array('query' => array('destination' => 'node'), 'absolute' => TRUE)), 'The user was redirected to the password form');
+
+    // Test that the password can be set to the stronger password.
+    $edit = array();
+    $edit['current_pass'] = 'Password1';
+    $edit['pass[pass1]'] = '35qzYI^HUbAZ';
+    $edit['pass[pass2]'] = '35qzYI^HUbAZ';
+    $this->drupalPost("user/{$this->web_user->uid}/edit", $edit, t('Save'));
+
+    // Make sure the user can log in with their new password.
+    $this->drupalLogout();
+    $this->web_user->pass_raw = '35qzYI^HUbAZ';
+    $this->drupalLogin($this->web_user);
+    $this->drupalLogout();
+  }
+
+  /**
+   * Tests user deletion.
+   */
+  public function testUserDeletion() {
+    // Delete web_user and verify password score gets cleaned up.
+    variable_set('user_cancel_method', 'user_cancel_reassign');
+    variable_set('password_strength_default_required_score', '0');
+    $this->drupalLogin($this->admin_user);
+    $this->drupalGet('user/' . $this->web_user->uid . '/edit');
+    $this->drupalPost(NULL, NULL, t('Cancel account'));
+    // Confirm deletion.
+    $this->drupalPost(NULL, NULL, t('Cancel account'));
+    $this->assertFalse(user_load($this->web_user->uid), 'User is not found in the database.');
+    $this->assertTrue(password_strength_get_user_score($this->web_user->uid) === NULL, t('User password score removed.'));
   }
 
   /**
