I am trying to convert my custom simple auth system in my rails app to use AuthLogic. I have managed to get everything working fairly easily, but now when I try to login it will not properly validate my credentials. The pertinent code is below:
# app/models/profile.rb
class Profile < ActiveRecord::Base
acts_as_authentic do |c|
c.transition_from_crypto_providers = Authlogic::CryptoProviders::Sha1,
c.crypto_provider = Authlogic::CryptoProviders::Sha512
end
end
I used to use this to hash my password on creation:
# app/models/profile.rb
def hash_password
self.salt = ActiveSupport::SecureRandom.base64(8)
self.hashed_password = Digest:SHA1.hexdigest(self.salt + #password)
end
I have already converted all the necessary table columns to be compatible with AuthLogic.
Is there a way to log what AuthLogic is hashing the password as? What else could be wrong?
I solved my problem, I had to write a custom crypto_provider, looked like this for anyone who is curious:
class MyCryptoProvider
# Turns your raw password into a Sha1 hash.
def self.encrypt(*tokens)
tokens = tokens.flatten
tokens = tokens.reverse
digest = Digest::SHA1.hexdigest([*tokens].join)
digest
end
# Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
def self.matches?(crypted, *tokens)
encrypt(*tokens) == crypted
end
end
Related
So, I'm new on a Rails 4 project and I must create a new way to authenticate users by API Keys. I need to generate them and then it must be shown only once to the user, and when it is saved in the Database, must be an encrypted value for security purposes.
This is my current class:
class ApiKey < ActiveRecord::Base
# Hexdigest the token's HMAC digest
HMAC_SECRET_KEY = Figaro.env.secret_key_base!
# Validate the token's HMAC digest
attr_accessor :token
#Relationships
belongs_to :bearer, polymorphic: true
#Callbacks
before_create :remove_previous_api_key_and_return_warning, :create_digested_api_key,
private
def remove_existing_api_key
# we need to remove all the existing api keys for the user before we create a new one.
#user = Aim::User.find_by(id: bearer_id)
#user.api_keys.destroy_all
end
def api_key_warning
# this method must return a string containing a warning message that will be shown to the user right after creating the API key
puts 'Please, save your just created API Key, it will not be shown again.'
end
def create_original_api_key
# this method must return a string containing the original API key that will be used to authenticate the user
token = SecureRandom.hex(32)
token
end
def remove_previous_api_key_and_return_warning
remove_existing_api_key
api_key_warning
end
def create_digested_api_key
self.token = create_original_api_key
raise ActiveRecord::RecordInvalid.new(self.token), 'token is required' unless token.present?
self.token_digest = OpenSSL::HMAC.hexdigest('SHA256', HMAC_SECRET_KEY, token)
digest = OpenSSL::HMAC.hexdigest('SHA256', HMAC_SECRET_KEY, token)
self.token_digest = digest
end
end
The data saved will be the result of the create_digested_api_key method and that is not intended. I tried using callbacks to return the original key before saving and inserting the digested key after saving, but despite it seems to work in the console, the original value is the saved one.
This will not have a controller to make crud operations in, the created API keys here, will be used to authenticate directly with the controller that handles that matter directly, so I could not generate the key in a create method inside the controller and return it from there. Any thoughts?
I've been trying to research implementing JWT into my application and a little confused. Currently, I'm using BCrypt in order to Hash and Salt. My file looks like this in the User model
class User < ApplicationRecord
has_secure_password
validates :username, :email, :password_digest, presence: true
validates :password, length: { minimum: 6, allow_nil: true }
attr_reader :password
after_initialize :ensure_session_token
def self.find_by_credentials(email, password)
user = User.find_by(email: email)
user && user.is_password?(password) ? user : nil
end
def self.generate_session_token
SecureRandom.urlsafe_base64
end
def password=(password)
#password = password
self.password_digest = BCrypt::Password.create(password)
end
def is_password?(password)
BCrypt::Password.new(self.password_digest).is_password?(password)
end
def reset_session_token!
self.session_token = User.generate_session_token
self.save!
self.session_token
end
def ensure_session_token
self.session_token ||= User.generate_session_token
end
end
What I'm trying to ask is would I need to create a new method to further encrypt the password output from using BCrypt? Like I can't find any articles where a user encrypts a user password with BCrypt then uses JWT. All I'm seeing is people mentioning adding BCrypt by adding has_secure_password to the user model and basically creating hashing methods with JWT instead.
My question is replace BCrypt with JWT or what are some recommendation in regards to securing a user password with both JWT and BCrypt? also, any beginner friendly articles would be appreciated.
Thanks for all your help and explanation.
JWT is quite a different way compared to the method of logging in a user and using sessions and cookies to authenticate future requests.
If you think of it like this, user comes to your application and goes to login (like normal, with Devise for example). You receive their username and password on the login and check agasint the BCrypt hash in the database. If they successfully login, you then provide them with a JWT token. Inside this token is encoded their user id.
When they make future requests to your application (normally from an API) then they will provide the token instead of their username password. Your server has a secret and can decrypt this token to check if it is valid and can then use the user_id inside to know that it is the correct user. This will allow them to access any resources that they have access to.
JWT is normally used for javascript front ends or Smart phone apps that want to have long login times (rather than session or cookie). The token is also stateless so as long as the server has the secret, it can check its valid and decrypt it.
Here is a more detailed explanation: https://github.com/dwyl/learn-json-web-tokens
Here is a good guide for setting up JWT with devise: https://medium.com/#mazik.wyry/rails-5-api-jwt-setup-in-minutes-using-devise-71670fd4ed03
I'm trying to implement a 3 legged authentication in my app to the Google API, to be able to access registered users' Google Calendars.
In the quickstart Ruby guide, this command comes up that as far as I understand should point to the user's tokens:
token_store = Google::Auth::Stores::FileTokenStore.new(file: CREDENTIALS_PATH)
It expects the tokens stored in a file (or Redis), but (of course) I store each user's tokens in my database (Postgres).
Have I understand the purpose of the command wrongly or otherwise - how do I use it with a database store?
Official documentation
I implemented it myself based on #Rafe's answer. Just wanted to share in case someone wants to copy the ActiveRecord / Database store implementation:
module Google
module Auth
module Stores
class DatabaseTokenStore < Google::Auth::TokenStore
def load(id)
user = User.find(id)
{
"client_id": ENV['google_client_id'],
"access_token": user.token,
"refresh_token": user.refresh_token,
"scope": ENV['google_scopes'],
"expiration_time_millis": user.token_expires_at
}.to_json
end
def store(id, token)
user = User.find(id)
hsh = JSON.parse(token)
user.update(
token: hsh["access_token"],
token_expires_at: hsh["expiration_time_millis"] / 1000
)
end
end
end
end
end
Implement it yourself, according to the the readme:
Custom storage implementations can also be used. See token_store.rb for additional details.
It shouldn't be too hard to implement the load(id), store(id, token) and delete(id) with ActiveRecord (or another ORM) by the looks of the mentioned files.
Accepted answer above it's good and I recommend it https://stackoverflow.com/a/48267763/473040, but I find it useful to store everything for future debugging. Also simplicity is beautiful :)
Add json column to Postgres DB table (or serialise text field in other db)
class AddGooglePhotosTokensToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :google_photo_tokens, :json
end
end
class:
class GoogleAuthDbStore < Google::Auth::TokenStore
def load(id)
user = User.find(id)
user.google_photo_tokens
end
def store(id, tokens)
user = User.find(id)
user.google_photo_tokens = tokens
user.save!
end
def delete id
user = User.find(id)
user.google_photo_tokens = nil
user.save!
end
end
use
def authorizer
return #authorizer if #authorizer
client = Google::Auth::ClientId.new(Rails.application.credentials.google.fetch(:client_id), Rails.application.credentials.google.fetch(:client_secret))
scope = ['https://www.googleapis.com/auth/photoslibrary.readonly']
token_store = Provider::GoogleAuthDbStore.new # <<<here
#authorizer = Google::Auth::WebUserAuthorizer.new(client, scope, token_store, '/auth/google/callback')
#authorizer
end
In my Rails app, I use devise to manage users with devise's default configuration. I need to md5 the user-provided password before going down into the devise layer. That is, two steps are included:
(1) (2)
password_in_plain --- password_in_md5 --- password_in_bcrypt.
The first one(1) is our concern, the last one(2) is not (devise takes care of it).
I generated two devise controllers: registrations and sessions, and added a before_filter to do the job -- md5 the plain password user provided. The user can be registered with a success, but login always fail. Here is my code:
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :md5_password_params, only: [:create]
protected
def md5_password_params
params[:user][:password] = Digest::MD5.hexdigest(params[:user][:password])
params[:user][:password_confirmation] = Digest::MD5.hexdigest(params[:user][:password_confirmation])
end
end
class Users::SessionsController < Devise::SessionsController
before_filter :md5_password, only: [:create]
protected
def md5_password
params[:user][:password] = Digest::MD5.hexdigest(params[:user][:password])
end
end
What's wrong here? Please help. Thanks in advance.
Background: one of my other server apps (written in c++) will create user records in the same database directly. For safety reason, a md5 password will be sent to this server when users make registrations in their phones.
Instead of doing this on the controller level and mucking about with the params you can create your own encryptor for Devise.
One major reason you would want to do this is that if your user fills in the registration form and submits it with invalid values both the password and confirmation fields will contain a MD5 digest of what the user originally typed. This is because you are mutating the params hash before it is fed to the model.
Submitting the form again means that the password is encrypted twice and no longer matches what the user thinks the original password is. On the plus side its extremely secure since nobody can actually guess their password ;)
Based on the current version of Devise you should be able to do something like the following to ensure the password is encrypted before a digest is created and before trying to compare plaintext with the encrypted password.
Make sure to add the devise-encryptable gem to your Gemfile. Its required for anything else than default encryption.
# config/initializers/md5.rb
require 'digest/md5'
module Devise
module Encryptable
module Encryptors
class BCryptPlusMD5 < Base
def self.digest(klass, password)
super(klass, Digest::MD5.hexdigest(password))
end
def self.compare(klass, hashed_password, password)
super(klass, hashed_password, Digest::MD5.hexdigest(password))
end
end
end
end
end
You would can configure what encryptor to use in config/initializers/devise.rb - however I have not been able to find a good source on how the class lookup works.
https://github.com/plataformatec/devise/blob/master/lib/devise/encryptor.rb
I'm trying to convert an existing Admin model to Devise. We already have a password hash but it's obviously not Devise compatible. What I would like to do is accept the login form and check the provided password against the encrypted password. If it's not correct, use the old hash to check the password and if it matches, empty the old password_hash field and set Devise's password to the provided password and save the model.
What's the best way to move forward? I suspect that I need to override something, perhaps in a custom controller, but I'm not entirely sure how to proceed.
You can let Devise do the "hard work" of encrypting the password with the new crypt scheme, as shown in https://gist.github.com/1704632:
class User < ActiveRecord::Base
alias :devise_valid_password? :valid_password?
def valid_password?(password)
begin
super(password)
rescue BCrypt::Errors::InvalidHash
return false unless Digest::SHA1.hexdigest(password) == encrypted_password
logger.info "User #{email} is using the old password hashing method, updating attribute."
self.password = password
true
end
end
end
Using the bcrypt encryptor in Devise, this is what I ended up doing with my legacy data:
In models/user.rb
# Because we have some old legacy users in the database, we need to override Devises method for checking if a password is valid.
# We first ask Devise if the password is valid, and if it throws an InvalidHash exception, we know that we're dealing with a
# legacy user, so we check the password against the SHA1 algorithm that was used to hash the password in the old database.
alias :devise_valid_password? :valid_password?
def valid_password?(password)
begin
devise_valid_password?(password)
rescue BCrypt::Errors::InvalidHash
Digest::SHA1.hexdigest(password) == encrypted_password
end
end
As you can see, devise throws an InvalidHash exception when it encounters an invalid hash, which it would do when authenticating a legacy user.
I use this to fall back to the hashing-algorithm used to create the original legacy hash.
It doesn't change the password though, but that could simply be added to the method if needed.
First you need to copy password_salt and encrypted_password to your new object model
Using this because I have to export my database User to another application and old,
app are using devise 1.0.x and new app using 2.1.x
Class User < ActiveRecord::Base
alias :devise_valid_password? :valid_password?
def valid_password?(password)
begin
devise_valid_password?(password)
rescue BCrypt::Errors::InvalidHash
salt = password_salt
digest = nil
10.times { digest = ::Digest::SHA1.hexdigest('--' << [salt, digest, password, nil].flatten.join('--') << '--') }
digest
return false unless digest == encrypted_password
logger.info "User #{email} is using the old password hashing method, updating attribute."
self.password = password
self.password_salt = nil # With this you will knew what object already using the new authentication by devise
self.save
true
end
end
end
If you're moving from SHA512, the solution is a bit more involved than moeffju's SHA1 solution:
def valid_password?(password)
if has_legacy_password?
return false unless valid_legacy_password?(password)
convert_legacy_password!(password)
true
else
super(password)
end
end
protected
def has_legacy_password?
password_salt.present?
end
def convert_legacy_password!(password)
self.password = password
self.password_salt = nil
self.save
end
def valid_legacy_password?(password)
stretches = 10
salt = password_salt
pepper = nil
digest = pepper
stretches.times do
tokens = [salt, digest, password, pepper]
digest = Digest::SHA512.hexdigest('--' << tokens.flatten.join('--') << '--')
end
Devise.secure_compare(encrypted_password, digest)
end
Be sure to replace stretches and pepper with the values you used to encrypt the passwords.
follow Thomas Dippel instructions i have made a gist that update password:
https://gist.github.com/1578362
# Because we have some old legacy users in the database, we need to override Devises method for checking if a password is valid.
# We first ask Devise if the password is valid, and if it throws an InvalidHash exception, we know that we're dealing with a
# legacy user, so we check the password against the SHA1 algorithm that was used to hash the password in the old database.
#SOURCES OF SOLUTION:
# http://stackoverflow.com/questions/6113375/converting-existing-password-hash-to-devise
# https://github.com/binarylogic/authlogic/blob/master/lib/authlogic/crypto_providers/sha512.rb
# https://github.com/plataformatec/devise/blob/master/lib/devise/encryptors/authlogic_sha512.rb
alias :devise_valid_password? :valid_password?
def valid_password?(password)
debugger
begin
devise_valid_password?(password)
rescue BCrypt::Errors::InvalidHash
stretches = 20
digest = [password, self.password_salt].flatten.join('')
stretches.times {digest = Digest::SHA512.hexdigest(digest)}
if digest == self.encrypted_password
#Here update old Authlogic SHA512 Password with new Devise ByCrypt password
# SOURCE: https://github.com/plataformatec/devise/blob/master/lib/devise/models/database_authenticatable.rb
# Digests the password using bcrypt.
# Default strategy for Devise is BCrypt
# def password_digest(password)
# ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
# end
self.encrypted_password = self.password_digest(password)
self.save
return true
else
# If not BCryt password and not old Authlogic SHA512 password Dosn't my user
return false
end
end
end