Rails 5 has_secure_token encryption - ruby-on-rails

In the Ruby on Rails has_secure_token gem/feature, it creates a unique token upon record creation and stores it in the database as plain text. If I am using that token to grant users access to an API, is there a security risk in storing that token as plain text in the database?
I was hoping there would be a way to encrypt the token column when the has_secure_token method commits the token to the database, similar to how bcrypt encrypts passwords into a database.
I have tried using gems such as attr_encrypted to store a hashed value of the token, but it seems to be incompatible with has_secure_token. Here is how my model is currently set up:
class User < ApplicationRecord
has_secure_token :token
# attr_encrypted :token, key: :encrypt_token, attribute: 'token'
# def encrypt_token
# SecureRandom.random_bytes(32)
# end
end
The commented code is attr_encrypted code that has proven to be incompatible. If someone knew if there was a way to safely encrypt a column in the database, while also using has_secure_token, I would greatly appreciate it!
Let me know if any more information is needed or this is confusing. Thanks in advance!

Rails 6 ActiveModel::SecurePassword#has_secure_password accepts an attribute name argument and will use BCrypt to set the value of a corresponding #{attribute}_digest column. The default attribute name is password and the model must have an accessor for a #{attribute}_digest attribute.
A simplified example with both a password and an api_token:
rails generate model User password_digest:string api_token_digest:string
Rails 6
class User < ApplicationRecord
has_secure_password #create a `password` attribute
has_secure_password :api_token, validations: false
before_create do
self.reset_token = SecureRandom.urlsafe_base64
end
end
Prior to Rails 6 you can directly invoke BCrypt to encrypt the token.
require 'bcrypt'
class User < ApplicationRecord
has_secure_password #create a `password` attribute
before_create do
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.api_token = SecureRandom.urlsafe_base64
self.api_token_digest = BCrypt::Password.create(api_token, cost: cost)
end
end

Related

Ruby Rails attr_encrypted format encrypted attribute

I am using attr_encrypted and I want to get formatted attribute whenever I access encrypted attribute - is there any way of doing this?
The model:
class User < ApplicationRecord
attr_encrypted :balance, key: 'some super secret key', marshal: true
end
And I want to be able to access balance and get Money object:
Money.new(balance, currency)
Is there possibility to make user.balance return this stright away (without additional methods like user.balance_to_money?
I have tried to somehow "extend" attr_encrypted behaviour (defined attribute getters) but I am not sure how to achieve this.
I have tried using custom Marshal object, but It won't work since I have to access user's currency from the database (Marshaler won't have access to this)
attr_encrypted :balance, key: 'some super secret key', marshal: true, marshaler: BalanceMarshaler
module BalanceMarshaler
extend self
def dump(data)
data.to_s
end
def load(data)
number = Marshal.load(data)
Money.new(number, currency)
end
end
If you need to maintain an accessor that treats balance as a Money constructor, you could sidestep the issue of overriding the behavior and just use a different column.
e.g. balance_new or whatever, and then create an accessor for .balance that creates a Money object from balance_new...

password_digest value is unique for user?

I am using rails 4 with has_secure_password which has password_digest in users table.i would like to store some unique value to cookie, password_digest is unique for user in users table? how to use it as unique token? can i?
As #JonathonReinhart said, don't re-use the password_digest, and since the authenticity_token for CSRF changes in the session for every form that is submitted, you can't use that here either. If you just need to generate a unique token for your User model, I recommend doing something like this:
rails generate migration AddAccessTokenToUser access_token:string:uniq:index
Then you can generate the token on create with a callback like so:
class User < ActiveRecord::Base
# Call backs
# ----------
before_create :generate_access_token
private
def generate_access_token
begin
self.access_token = SecureRandom.hex
end while self.class.exists?(access_token: access_token)
end
end
The begin-end-while will check that the SecureRandom.hex value will always be unique in the table.
Once you have this token, you can use it in a cookie or wherever.

Modify Devise SAML Attributes

I'm using Rails 4 and Devise with Devise SAML Authenticatable for my Account system.
I've got the SAML working and all, but am trying to work out one thing.
I'd like to change one of the SAML attributes before saving it (since it is formatted incorrectly). Essentially, the Account's SAML request is given a role attribute which is one of the following Group_admin, Group_consumer, Group_supplier. I have a role field in my Account model enumerated as follows:
enum role: [:admin, :consumer, :supplier]
Clearly I can't directly set role because Group_admin != admin (etc.). Is there a way to modify the SAML attribute that is given before Devise saves the field?
I've tried a before_save filter to no avail.
before_save :fix_role!
private
def fix_role!
self.role = self.role.split('_')[1]
end
Does anyone know of a way to do this? I can post any other code if necessary, I'm just not sure what else is needed. Thanks.
I was able to do the following to fix the problem:
attribute-map.yml
"role": "full_role"
account.rb
before_save :set_role!
attr_accessor :full_role
private
def set_role!
self.role = self.full_role.split('_')[1]
end
Essentially, I used an attr_accessor to store the incorrectly formatted role given from the SAML response and a before_save filter to correctly set the "real" role field.

attr_encrypted and devise, Encrypt user data with users password

I'm using the attr_encrypted gem and I got also devise installed in my environment.
I got a user model this is handled by devise and the database column is:
encrypted_password
Users can save clients and I want to encrypt the clients name and age with the users password.
my client.rb file looks like this:
Here the data gets encrypted successfully.
class Client < ActiveRecord::Base
attr_accessor :name :age
attr_encrypted :name, :age, key: "test1234"
But I'd like to encrypt the data with the Users.password.
Something like so:
class Client < ActiveRecord::Base
attr_accessor :name :age
attr_encrypted :name, :age, key: current_user.encrypted_password
The current_user is the Devise helper method but since this is from a session I can't access it in a model.
Basically I'd like to encrypt all the clients stuff with users password.
But If I do that with the encrypted_password then I already got the password to decrypt the whole field.
I want to provide security to my users and I don't want to know or be able to view their data.
So the only way to do this is by encrypting all the data with the prehashed devise users password?
edit:
The user.encrypted_password is already hashed and whenever I access the db - I can use this to decrypt all the data right?
So I should request the users password -> hash it like devise does - compare it with the users.encrypted_password?
Do I have a logic error somewhere ?
How would you solve this?
attr_encrypted provides a way to specify an instance method to provide the key.
class Client < ActiveRecord::Base
attr_encrypted :name, :age, key: :client_key
def client_key
# just assuming relation between Client and User
self.user.encrypted_password
end
end
Source: https://github.com/attr-encrypted/attr_encrypted#symbols-representing-instance-methods-as-keys
As you using Devise it uses bcrypt algorithm to encrypt your password which is one way encryption
ie this process is not reversible, there's no way to go from the hash back to the password.
so you can use that hash for encrypting the whole data.
But my suggestion would be you use bcrypt algorithm for encrypting your data rather than using user password,reason why i am suggesting bcrypt rather than using your password a hash to encrypt your data
You will have re-encrypt you data each and every time when the user
changes his password If you fail to do so in any occasion you wont
be able to retrive you data back.
The overhead will more ie each time re-encrypting the data on
password change
The encrypted_password will be very tightly coupled with the user
data. I feel that the user data should be independent of password
related to access and there should be a different independent
encrypting for use data which is not related to user login or
password
You can also ref : https://github.com/codahale/bcrypt-ruby

Rails/Devise changing min length of username

Rails 3.0
Authlogic 2.1.6
Using username (not email) for authentication.
Authlogic requires username to be at least 3 characters.
How does one convince Authologic to allow a 2 character username?
Thanks for your help.
You can override the authlogic default username length by adding the following code to models/user.rb
class User < ActiveRecord::Base
acts_as_authentic do |c|
c.merge_validates_length_of_login_field_options :within => 4..100
end
#....
end
This assumes your user model is called User

Resources