This plugin forces a two step login process. After entering username/email and password, you must then answer a challenge question. Unlike security questions which are typically used when doing things like changing passwords, the challenge question must be answered each time logging in. You can set which users or admins need to answer a challenge question. We will assume your resource is User for the remainder of the Readme.
- configure max challenge question attempts
- configure number of challenge questions stored for each user
- configure number of challenge questions asked for each user
- per user level control if he really need challenge question authentication
In a Rails environment, require the gem in your Gemfile:
gem 'devise_challenge_questionable'
Once that's done, run:
bundle install
In order to add challenge questions to a model, run the command:
bundle exec rails g devise_challenge_questionable MODEL
bundle exec rails g devise_challenge_questionable:install
bundle exec rails g devise_challenge_questionable:views users
Where MODEL is your model name (e.g. User or Admin). This generator will add :challenge_questionable
to your model
and create a migration in db/migrate/
, which will add :reset_challenge_questions_token
and :challenge_question_failed_attempts
to your table.
Finally, run the migration with:
bundle exec rake db:migrate
To manually enable challenge questions for the User model, you should add the following. Set up relationships. You should already have a devise line so you would just add :challenge_questionable to it. Also, you need to allow accessibility to :user_challenge_questions_attributes
. Replace user with whatever resource you are using. Typically it would be user or admin.
has_many :user_challenge_questions, :validate => true, :inverse_of => :user
accepts_nested_attributes_for :user_challenge_questions, :allow_destroy => true
devise :challenge_questionable
attr_accessible :user_challenge_questions_attributes
You also need to add the user_challenge_question.rb
Model.
class UserChallengeQuestion < ActiveRecord::Base
belongs_to :user
validates :challenge_question, :challenge_answer, :presence => true
validates :challenge_answer, :length => { :in => 4..56 }, :format => {:with => /^[\w\s:]*$/, :message => "can not contain special characters"}, :allow_blank => true
# Must use custom validation since uniqueness scope will not work with has_many association
validate :challenge_question_uniqueness
validate :challenge_answer_uniqueness
validate :challenge_answer_repeating
before_save :digest_challenge_answer
def digest_challenge_answer
if ENV['PASSWORD_PEPPER']
write_attribute(:challenge_answer, ::BCrypt::Password.create(self.challenge_answer.downcase + ENV['PASSWORD_PEPPER'], :cost => Devise.stretches)) unless self.challenge_answer.nil?
else
write_attribute(:challenge_answer, ::BCrypt::Password.create(self.challenge_answer.downcase, :cost => Devise.stretches)) unless self.challenge_answer.nil?
end
end
private
def challenge_question_uniqueness
if self.challenge_question.present? && self.user.user_challenge_questions.select{|q| q.challenge_question == self.challenge_question}.count > 1
errors.add(:challenge_question, 'can only be used once')
end
end
def challenge_answer_uniqueness
if self.challenge_answer.present? && self.user.user_challenge_questions.select{|q| q.challenge_answer == self.challenge_answer}.count > 1
errors.add(:challenge_answer, 'can only be used once')
end
end
def challenge_answer_repeating
if self.challenge_answer.present? && self.challenge_answer =~ /(.)\1{2,}/
errors.add(:challenge_answer, 'can not have more then two repeating characters in a row')
end
end
end
Locales:
en:
activerecord:
attributes:
user:
user_challenge_questions:
challenge_question: "Question"
challenge_answer: "Answer"
devise:
challenge_questions:
attempt_failed: "Attempt failed."
updated_challenge_questions: "Successfully updated challenge questions."
send_instructions: "You will receive an email with instructions about how to reset your challenge questions in a few minutes."
Configuration settings
# ==> Configuration for :challenge_questionable
# Max challenge question attempts
config.max_challenge_question_attempts = 3
# Number of challenge questions to store for each user
config.number_of_challenge_questions = 3
# Default challenge question options
config.challenge_questions = ["What was your high school mascot?","In which city was your first elementary school?","In which city was your mother born?","What is the name of your favorite movie?","Who is your favorite athlete?","What was your most memorable gift as a child?","What is your favorite cartoon character?","What is the name of your favorite novel?","Name of favorite childhood pet?","What is the name of your elementary school?","What is your youngest child's middle name?","Last Name of your kindergarten teacher?","What is the first name of your grandmother (your father's mother)?","What is your spouse's nickname?","Name of the place where your wedding reception was held?","Name of a college you applied to but did not attend?","What is the first name of the youngest of your siblings?","What is the first name of the eldest of your siblings?","What is your favorite television show?","If you needed a new first name, what would it be?","What is the first name of your youngest child?","When is your mother's birthday (MM/DD)?","What is your eldest child's middle name?","What is the last name of the funniest friend you know?","Name the highest mountain you've been to the top of?","What is the first name of your grandmother (your mother's mother)?","What is the first name of your grandfather (your mother's father)?","What was the first name of your best man/maid of honor?","What was the last name of your first grade teacher?","What is the last name of your first boyfriend or girlfriend?","Which high school did you attend?","What was your major during college?","What was the name of your first pet?","What was your favorite place from your childhood?","What is your favorite song?","What is your favorite car?","What is your mother’s middle name?","What is the (MM/DD) of your employment?","What is the make/model of first car?","What is the name of the city or town where you were born?","What is the name of your favorite childhood teacher?","What is the name of your favorite childhood friend?","What are your oldest sibling’s (MM/YYYY) of birth?","What is your oldest sibling’s middle name?","What school did you attend for sixth grade?","On what street did you live in third grade?"]
By default challenge questions are enabled for each user, you can change it with this method in your User model:
def login_challenge_questions?(request)
request.ip != '127.0.0.1'
end
def set_challenge_questions?(request)
request.ip != '127.0.0.1'
end
this will disable challenge questions for local users