Rails 5 has_secure_token encryption
Asked Answered
P

1

6

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!

Propitiatory answered 10/1, 2018 at 18:18 Comment(1)
it all depends on your usecase: if it's truly a token (it is usable once and then gets recreated, such as a password reset token) then yes, it is probably pretty save to store it as plain text. however, your usecase seems to imply that you think of the token more as a password replacement, without any form of automatic invalidation. in this case i would strongly advise against storing it as plain text (or even using has_secure_token!) also: your API better use HTTPS :)Zulemazullo
B
5

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
Bioscope answered 26/4, 2019 at 5:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.