Since we want to write our own Authentication Implementation, we must follow suit and write a Model.

require 'devise/strategies/custom_authenticatable'moduleDevisedefself.bcrypt(klass, password)
ActiveSupport::Deprecation.warn "Devise.bcrypt is deprecated; use Devise::Encryptor.digest instead"Devise::Encryptor.digest(klass, password)
endmoduleModels# Authenticatable Module, responsible for hashing the password and# validating the authenticity of a user while signing in.## == Options## DatabaseAuthenticatable adds the following options to devise_for:## * +pepper+: a random string used to provide a more secure hash. Use# `rake secret` to generate new keys.## * +stretches+: the cost given to bcrypt.## == Examples## User.find(1).valid_password?('password123') # returns true/false#moduleCustomAuthenticatableextendActiveSupport::Concern
included do
after_update :send_password_change_notification, if: :send_password_change_notification?attr_reader:password, :current_passwordattr_accessor:password_confirmationenddefself.required_fields(klass)
[:encrypted_password]+ klass.authentication_keys
end# Generates a hashed password based on the given value.# For legacy reasons, we use `encrypted_password` to store# the hashed password.defpassword=(new_password)
@password= new_password
self.encrypted_password = password_digest(@password) if@password.present?
end# Verifies whether a password (ie from sign in) is the user password.defvalid_password?(password)
Devise::Encryptor.compare(self.class, encrypted_password, password)
end# Set password and password confirmation to nildefclean_up_passwordsself.password =self.password_confirmation =nilend# Update record attributes when :current_password matches, otherwise# returns error on :current_password.## This method also rejects the password field if it is blank (allowing# users to change relevant information like the e-mail without changing# their password). In case the password field is rejected, the confirmation# is also rejected as long as it is also blank.defupdate_with_password(params, *options)
current_password = params.delete(:current_password)
if params[:password].blank?
params.delete(:password)
params.delete(:password_confirmation) if params[:password_confirmation].blank?
end
result =if valid_password?(current_password)
update_attributes(params, *options)
elseself.assign_attributes(params, *options)
self.valid?
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
falseend
clean_up_passwords
result
end# Updates record attributes without asking for the current password.# Never allows a change to the current password. If you are using this# method, you should probably override this method to protect other# attributes you would not like to be updated without a password.## Example:## def update_without_password(params, *options)# params.delete(:email)# super(params)# end#defupdate_without_password(params, *options)
params.delete(:password)
params.delete(:password_confirmation)
result = update_attributes(params, *options)
clean_up_passwords
result
end# Destroy record when :current_password matches, otherwise returns# error on :current_password. It also automatically rejects# :current_password if it is blank.defdestroy_with_password(current_password)
result =if valid_password?(current_password)
destroy
elseself.valid?
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
falseend
result
end# A callback initiated after successfully authenticating. This can be# used to insert your own logic that is only run after the user successfully# authenticates.## Example:## def after_database_authentication# self.update_attribute(:invite_code, nil)# end#defafter_database_authenticationend# A reliable way to expose the salt regardless of the implementation.defauthenticatable_salt
encrypted_password[0,29]if encrypted_password
enddefsend_password_change_notification
send_devise_notification(:password_change)
endprotected# Hashes the password using bcrypt. Custom hash functions should override# this method to apply their own algorithm.## See https://github.com/plataformatec/devise-encryptable for examples# of other hashing engines.defpassword_digest(password)
Devise::Encryptor.digest(self.class, password)
enddefsend_password_change_notification?self.class.send_password_change_notification && encrypted_password_changed?
endmoduleClassMethodsDevise::Models.config(self, :pepper, :stretches, :send_password_change_notification)
# We assume this method already gets the sanitized values from the# DatabaseAuthenticatable strategy. If you are using this method on# your own, be sure to sanitize the conditions hash to only include# the proper fields.deffind_for_database_authentication(conditions)
find_for_authentication(conditions)
endendendendend

Next we need to register our Model with Devise. We use Devise#add_module to do this. We can do this inside the file devise.rb that you can see in the folder config/initializers. Just add it to the top of the file.

Without all of those options, Devise and Rails’ Router won’t know to delegate Requests to SessionController into your Module.

Warden Strategy

A strategy is a place where you can put logic related to authentication. Any strategy inherits from Warden::Strategies::Base.

The Warden::Strategies.add method is a simple way to provide custom strategies. You must declare an authenticate! method. You may provide a valid? method. The valid method should return true or false depending on if the strategy is a valid one for the request.