- Published on
- • 2 min read
Password Policy Implementation with Devise and Devise Security Extension
- Authors

- Name
- Shaiju Edakulangara
- @eshaiju
One of my recent projects was in the banking domain, so I had to implement password policies. For that, I used the Devise gem.
Password policies:
- Enforce password history - 5 passwords should be remembered.
- Maximum password age - 30 days.
- Minimum password length - 10 letters.
- Password complexity - Should be a combination of letters, numbers, and symbols.
- Account lockout threshold - 5 failed attempts.
- Account lockout duration - 30 minutes.
- Email validation - Accept only emails from allowed domains.
Most requirements are achievable with simple configurations in the Devise initializer. To implement password expiration, archiving, and complexity, I used devise_security_extension.
1. Installation
Add the gem to your Gemfile:
gem 'devise_security_extension'
Run the generator:
rails generate devise_security_extension:install
Add the modules to your Devise model (e.g., User):
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable,
:password_expirable, :password_archivable, :expirable, :lockable
end
2. Enforce password history
Uncomment the configuration in the Devise initializer and create the old_passwords table:
# config/initializers/devise.rb
Devise.setup do |config|
config.password_archiving_count = 5
config.deny_old_passwords = true
end
Migration for old passwords:
create_table :old_passwords do |t|
t.string :encrypted_password, :null => false
t.string :password_salt
t.string :password_archivable_type, :null => false
t.integer :password_archivable_id, :null => false
t.datetime :created_at
end
add_index :old_passwords, [:password_archivable_type, :password_archivable_id], :name => :index_password_archivable
3. Password Age and Length
# config/initializers/devise.rb
config.expire_password_after = 1.months
config.password_length = 10..128
Migration for password tracking:
add_column :users, :password_changed_at, :datetime
add_index :users, :password_changed_at
4. Complexity and Lockout
# Complexity Regex: Need uppercase, lowercase, digit and special character
config.password_regex = /(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[\W])/
# Account Lockout
config.maximum_attempts = 5
config.unlock_in = 30.minutes
5. Email Domain Validation
Add a custom validator to your model:
validate :presence_of_domain_in_email
def presence_of_domain_in_email
email_domain = email.split("@").last.downcase
allowed_domains = ['gmail.com', 'example.com', 'example1.in']
unless allowed_domains.include?(email_domain)
errors.add :email, "This email domain is not valid."
end
end