One of my recent project was in banking domain. So I had to implement password policies, for that I used devise gem.
Password policies :
1 - Enforce password history - 5 password should be remembered
2 - maximum password age - 30 days
3 - minimum password length - 10 letters
4 - password must meet complexity requirements - Should be a combination of letters, numbers and symbols
5 - Account lockout threshold -5
6 - Account lockout duration - 30 minutes
7 - Email validation - Accept only emails of allowed set of domains
Most of the requirents mentioned above are achivable with simple cofigurations in Devise initializer. But to implement Password expirable, Password archivable and password complexity requirements check I used security extension devise_security_extension.
In this post I assume that we already had devise setup in our project. Now we need to add devise_security_extension in to our project.
1
gem 'devise_security_extension'
After you installed Devise Security Extension you need to run the generator:
1
rails generate devise_security_extension:install
The generator will inject the available configuration options into the existing Devise initializer.When you are done, you are ready to add Devise Security Extension modules on top of Devise modules to any of your Devise models.
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable,
:password_expirable, :password_archivable, :expirable, :lockable
end
1 - Enforce password history
Uncomment configuration password_archiving_count and deny_old_passwords in Devise initializer and also add a migration creating old_passwords tables.
1234567
Devise.setup do |config|
# How often save old passwords in archive
config.password_archiving_count = 5
# Deny old password (true, false, count)
config.deny_old_passwords = true
end
2 - maximum password age - 30 days
Uncomment configuration expire_password_after and change to 1.months and also add a migration to store .
1
# config.expire_password_after = 1.months
12345
create_table :the_resources do |t|
# other devise fields
t.datetime :password_changed_at
end
add_index :the_resources, :password_changed_at
Replace our devise model with the_resources.
12345
create_table :users do |t|
# other devise fields
t.datetime :password_changed_at
end
add_index :users, :password_changed_at
3 - minimum password length - 10 letters
12
# Range for password length.
config.password_length = 10..128
4 - password must meet complexity requirements - Should be a combination of letters, numbers and symbols Add password_regex with Regular expression which satisfies our needs.
12
# Need 1 char of A-Z, a-z and 0-9 and special charactor
config.password_regex = /(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[\W])/
5 - Account lockout threshold -5
123
# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
config.maximum_attempts = 5
6 - Account lockout duration - 30 minutes
12
# Time interval to unlock the account if :time is enabled as unlock_strategy.
config.unlock_in = 30.minutes
7 - Email validation - Accept only emails of allowed set of domains
For this, I added a custom validator which will check the domain name of user entered an email.
123456789101112131415161718
validate :presence_of_domain_in_email
def presence_of_domain_in_email
email_domain = email.split("@").last.downcase
allowed_domains = User.allowed_domains
unless allowed_domains[email_domain]
errors.add :email, "This email domain is not valid. "
end
end
def self.allowed_domains
{
'gmail.com' =>'gmail.com',
'example.com' => 'example.com',
'example1.in' =>'example1.in'
}
end