New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add has_secure_token to Active Record #18217

Merged
merged 1 commit into from Jan 4, 2015

Conversation

Projects
None yet
@robertomiranda
Contributor

robertomiranda commented Dec 27, 2014

I take the audacity of include has_scure_token to Active Model since the implementation is using methods that are implemented in most of popular ORMs or ODMs in ruby, like exists?and save, is just as suggestion and for sure I'm open to move it to Active Record anyway 馃槃. Is still missing documentation but I'd like to hear first opinions about the implementation/code

Usage

class User < ActiveRecord::Base
  has_secure_token :token1, :token2, key_length: 30
end

user = User.new
user.save
user.token1 # => "973acd04bc627d6a0e31200b74e2236"
user.token2 # => "e2426a93718d1817a43abbaa8508223"
user.regenerate_token1! # => true
user.regenerate_token2! # => true

ref #16879

cc @dhh

@simi

This comment has been minimized.

Show comment
Hide comment
@simi

simi Dec 27, 2014

Contributor

quoting @dhh from #16879 (comment):

Not interested in a heavily configurable thing. If someone wants to go hogwilld with that, it'll work great in a plugin. But for core, I'd just like something that works without configuration.

I think this introduces a lot of magic in contrast of has_secure_password implementation.

has_secure_password is all-in-one solution with 1 option only.

I think it is good idea to follow that simplicity so usecase will be:

class User < ActiveRecord::Base
  # add token column in migration
  has_secure_token
end

user = User.new
user.save
user.token # => "973acd04bc627d6a0e31200b74e2236"
user.regenerate_token! # => true

If this this simple solution is not enough for you, you'll need to roll your own then.

Contributor

simi commented Dec 27, 2014

quoting @dhh from #16879 (comment):

Not interested in a heavily configurable thing. If someone wants to go hogwilld with that, it'll work great in a plugin. But for core, I'd just like something that works without configuration.

I think this introduces a lot of magic in contrast of has_secure_password implementation.

has_secure_password is all-in-one solution with 1 option only.

I think it is good idea to follow that simplicity so usecase will be:

class User < ActiveRecord::Base
  # add token column in migration
  has_secure_token
end

user = User.new
user.save
user.token # => "973acd04bc627d6a0e31200b74e2236"
user.regenerate_token! # => true

If this this simple solution is not enough for you, you'll need to roll your own then.

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

Allowing you to name the token being added or to have more than one does not qualify as "heavy configuration" in my book. I support both of those. What I wasn't interested in was configuration of the token itself. That should just work.

On Dec 27, 2014, at 03:19, Josef 艩im谩nek notifications@github.com wrote:

quoting @dhh from #16879 (comment):

Not interested in a heavily configurable thing. If someone wants to go hogwilld with that, it'll work great in a plugin. But for core, I'd just like something that works without configuration.

I think this introduces a lot of magic in contrast of has_secure_password implementation.

has_secure_password is all-in-one solution with 1 option only.

I think it is good idea to follow that simplicity so usecase will be:

class User < ActiveRecord::Base

add token column in migration

has_secure_token
end

user = User.new
user.save
user.token # => "973acd04bc627d6a0e31200b74e2236"
user.regenerate_token! # => true
If this this simple solution is not enough for you, you'll need to roll your own then.


Reply to this email directly or view it on GitHub.

Member

dhh commented Dec 27, 2014

Allowing you to name the token being added or to have more than one does not qualify as "heavy configuration" in my book. I support both of those. What I wasn't interested in was configuration of the token itself. That should just work.

On Dec 27, 2014, at 03:19, Josef 艩im谩nek notifications@github.com wrote:

quoting @dhh from #16879 (comment):

Not interested in a heavily configurable thing. If someone wants to go hogwilld with that, it'll work great in a plugin. But for core, I'd just like something that works without configuration.

I think this introduces a lot of magic in contrast of has_secure_password implementation.

has_secure_password is all-in-one solution with 1 option only.

I think it is good idea to follow that simplicity so usecase will be:

class User < ActiveRecord::Base

add token column in migration

has_secure_token
end

user = User.new
user.save
user.token # => "973acd04bc627d6a0e31200b74e2236"
user.regenerate_token! # => true
If this this simple solution is not enough for you, you'll need to roll your own then.


Reply to this email directly or view it on GitHub.

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

Although I do actually like the option of passing no options to has_secure_token and then having it default to a token name of "token".

On Dec 27, 2014, at 03:19, Josef 艩im谩nek notifications@github.com wrote:

quoting @dhh from #16879 (comment):

Not interested in a heavily configurable thing. If someone wants to go hogwilld with that, it'll work great in a plugin. But for core, I'd just like something that works without configuration.

I think this introduces a lot of magic in contrast of has_secure_password implementation.

has_secure_password is all-in-one solution with 1 option only.

I think it is good idea to follow that simplicity so usecase will be:

class User < ActiveRecord::Base

add token column in migration

has_secure_token
end

user = User.new
user.save
user.token # => "973acd04bc627d6a0e31200b74e2236"
user.regenerate_token! # => true
If this this simple solution is not enough for you, you'll need to roll your own then.


Reply to this email directly or view it on GitHub.

Member

dhh commented Dec 27, 2014

Although I do actually like the option of passing no options to has_secure_token and then having it default to a token name of "token".

On Dec 27, 2014, at 03:19, Josef 艩im谩nek notifications@github.com wrote:

quoting @dhh from #16879 (comment):

Not interested in a heavily configurable thing. If someone wants to go hogwilld with that, it'll work great in a plugin. But for core, I'd just like something that works without configuration.

I think this introduces a lot of magic in contrast of has_secure_password implementation.

has_secure_password is all-in-one solution with 1 option only.

I think it is good idea to follow that simplicity so usecase will be:

class User < ActiveRecord::Base

add token column in migration

has_secure_token
end

user = User.new
user.save
user.token # => "973acd04bc627d6a0e31200b74e2236"
user.regenerate_token! # => true
If this this simple solution is not enough for you, you'll need to roll your own then.


Reply to this email directly or view it on GitHub.

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

Speaking of options. What justification are we using for key_length? Why would you want to change that number? Unless we have a very compelling reason, we should mix that.

On Dec 27, 2014, at 03:19, Josef 艩im谩nek notifications@github.com wrote:

quoting @dhh from #16879 (comment):

Not interested in a heavily configurable thing. If someone wants to go hogwilld with that, it'll work great in a plugin. But for core, I'd just like something that works without configuration.

I think this introduces a lot of magic in contrast of has_secure_password implementation.

has_secure_password is all-in-one solution with 1 option only.

I think it is good idea to follow that simplicity so usecase will be:

class User < ActiveRecord::Base

add token column in migration

has_secure_token
end

user = User.new
user.save
user.token # => "973acd04bc627d6a0e31200b74e2236"
user.regenerate_token! # => true
If this this simple solution is not enough for you, you'll need to roll your own then.


Reply to this email directly or view it on GitHub.

Member

dhh commented Dec 27, 2014

Speaking of options. What justification are we using for key_length? Why would you want to change that number? Unless we have a very compelling reason, we should mix that.

On Dec 27, 2014, at 03:19, Josef 艩im谩nek notifications@github.com wrote:

quoting @dhh from #16879 (comment):

Not interested in a heavily configurable thing. If someone wants to go hogwilld with that, it'll work great in a plugin. But for core, I'd just like something that works without configuration.

I think this introduces a lot of magic in contrast of has_secure_password implementation.

has_secure_password is all-in-one solution with 1 option only.

I think it is good idea to follow that simplicity so usecase will be:

class User < ActiveRecord::Base

add token column in migration

has_secure_token
end

user = User.new
user.save
user.token # => "973acd04bc627d6a0e31200b74e2236"
user.regenerate_token! # => true
If this this simple solution is not enough for you, you'll need to roll your own then.


Reply to this email directly or view it on GitHub.

@matthewd

This comment has been minimized.

Show comment
Hide comment
@matthewd

matthewd Dec 27, 2014

Member

I don't think multiple tokens is such a common use case that we need to do a *names thing -- one per call seems fine, and gives us a simpler API to maintain compatibility with in future.

And as it's apparently necessary, I'll join the existing chorus: this needs to be part of ActiveRecord, not ActiveModel.

Member

matthewd commented Dec 27, 2014

I don't think multiple tokens is such a common use case that we need to do a *names thing -- one per call seems fine, and gives us a simpler API to maintain compatibility with in future.

And as it's apparently necessary, I'll join the existing chorus: this needs to be part of ActiveRecord, not ActiveModel.

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

I鈥檇 be happy with it being one call per token. Don鈥檛 have a strong opinion on AR vs AM, but if others do, that鈥檚 fine.

On Dec 27, 2014, at 8:29 AM, Matthew Draper notifications@github.com wrote:

I don't think multiple tokens is such a common use case that we need to do a *names thing -- one per call seems fine, and gives us a simpler API to maintain compatibility with in future.

And as it's apparently necessary, I'll join the #16879 (comment) existing #16879 (comment) chorus #16879 (comment): this needs to be part of ActiveRecord, not ActiveModel.


Reply to this email directly or view it on GitHub #18217 (comment).

Member

dhh commented Dec 27, 2014

I鈥檇 be happy with it being one call per token. Don鈥檛 have a strong opinion on AR vs AM, but if others do, that鈥檚 fine.

On Dec 27, 2014, at 8:29 AM, Matthew Draper notifications@github.com wrote:

I don't think multiple tokens is such a common use case that we need to do a *names thing -- one per call seems fine, and gives us a simpler API to maintain compatibility with in future.

And as it's apparently necessary, I'll join the #16879 (comment) existing #16879 (comment) chorus #16879 (comment): this needs to be part of ActiveRecord, not ActiveModel.


Reply to this email directly or view it on GitHub #18217 (comment).

@simi

This comment has been minimized.

Show comment
Hide comment
@simi

simi Dec 27, 2014

Contributor

I think this should be part of ActiveModel and just that part checking existing token should be part of ActiveRecord. It will be easy to integrate this into another ActiveModel based libraries like Mongoid.

Contributor

simi commented Dec 27, 2014

I think this should be part of ActiveModel and just that part checking existing token should be part of ActiveRecord. It will be easy to integrate this into another ActiveModel based libraries like Mongoid.

Show outdated Hide outdated activemodel/lib/active_model/secure_token.rb
# Load securerandom only when has_secure_key is used.
require 'securerandom'
include InstanceMethodsOnActivation
cattr_accessor :token_columns, :options

This comment has been minimized.

@dhh

dhh Dec 27, 2014

Member

What's the token_columns variable for?

@dhh

dhh Dec 27, 2014

Member

What's the token_columns variable for?

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

Actually, I'd say that as it was mentioned elsewhere, that this SecureRandom implementation is unlikely enough to generate a duplicate, then we don't need the #exists? check. Someone could just implement that as a column constraint or similar. Then this thing becomes even simpler and doesn't require any dependency on AR at all.

Member

dhh commented Dec 27, 2014

Actually, I'd say that as it was mentioned elsewhere, that this SecureRandom implementation is unlikely enough to generate a duplicate, then we don't need the #exists? check. Someone could just implement that as a column constraint or similar. Then this thing becomes even simpler and doesn't require any dependency on AR at all.

@robertomiranda

This comment has been minimized.

Show comment
Hide comment
@robertomiranda

robertomiranda Dec 27, 2014

Contributor

@dhh 馃憤

Contributor

robertomiranda commented Dec 27, 2014

@dhh 馃憤

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

I boiled this down to its essence:

module ActiveModel
  module SecureToken
    extend ActiveSupport::Concern

    module ClassMethods
      # Example using Active Record (which automatically includes ActiveModel::SecurePassword):
      #
      #   # Schema: User(auth_token:string, invitation_token:string)
      #   class User < ActiveRecord::Base
      #     has_secure_token :auth_token
      #     has_secure_token :invitation_token
      #   end
      #
      #   user = User.create
      #   user.auth_token # => "44539a6a59835a4ee9d7b112"
      #   user.invitation_token # => "226dd46af6be78953bde1648"
      #   user.regenerate_auth_token # => true
      #   user.regenerate_invitation_token # => true
      #
      # SecureRandom is used to generate the 24-character unique token, so collisions are highly unlikely.
      # But you're still encouraged to enforce uniqueness, if that's required, by something like a unique index
      # in the database or through a validation check.
      def has_secure_token(attribute)
        require 'securerandom'
        define_method("regenerate_#{attribute}") { update! attribute => SecureRandom.hex(12) }
        before_create { self[attribute] = SecureRandom.hex(12) }
      end
    end
  end
end
Member

dhh commented Dec 27, 2014

I boiled this down to its essence:

module ActiveModel
  module SecureToken
    extend ActiveSupport::Concern

    module ClassMethods
      # Example using Active Record (which automatically includes ActiveModel::SecurePassword):
      #
      #   # Schema: User(auth_token:string, invitation_token:string)
      #   class User < ActiveRecord::Base
      #     has_secure_token :auth_token
      #     has_secure_token :invitation_token
      #   end
      #
      #   user = User.create
      #   user.auth_token # => "44539a6a59835a4ee9d7b112"
      #   user.invitation_token # => "226dd46af6be78953bde1648"
      #   user.regenerate_auth_token # => true
      #   user.regenerate_invitation_token # => true
      #
      # SecureRandom is used to generate the 24-character unique token, so collisions are highly unlikely.
      # But you're still encouraged to enforce uniqueness, if that's required, by something like a unique index
      # in the database or through a validation check.
      def has_secure_token(attribute)
        require 'securerandom'
        define_method("regenerate_#{attribute}") { update! attribute => SecureRandom.hex(12) }
        before_create { self[attribute] = SecureRandom.hex(12) }
      end
    end
  end
end
@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

Sorry, that should be has_secure_token(attribute = 'token') to enable the default case as well.

Member

dhh commented Dec 27, 2014

Sorry, that should be has_secure_token(attribute = 'token') to enable the default case as well.

@simi

This comment has been minimized.

Show comment
Hide comment
@simi

simi Dec 27, 2014

Contributor

And I think it is better to call that regenerate method reset_token! .

@dhh 馃憤

Contributor

simi commented Dec 27, 2014

And I think it is better to call that regenerate method reset_token! .

@dhh 馃憤

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

We already use the word reset to signify returning an attribute to its original state after modification, so that won't work well.

Member

dhh commented Dec 27, 2014

We already use the word reset to signify returning an attribute to its original state after modification, so that won't work well.

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

Still on the fence about whether leaning exclusively on a unique index or the like to ensure no collisions is the right move here. The whole point is to make this as simple as possible. If it needs a db unique index to work, it loses a fair amount of that simplicity (even if the implantation obviously becomes much simpler).

Member

dhh commented Dec 27, 2014

Still on the fence about whether leaning exclusively on a unique index or the like to ensure no collisions is the right move here. The whole point is to make this as simple as possible. If it needs a db unique index to work, it loses a fair amount of that simplicity (even if the implantation obviously becomes much simpler).

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

Here's another stab at a version that includes collision checking/mitigation:

      def has_secure_token(attribute)
        require 'securerandom'
        define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) }
        before_create { self[attribute] = self.class.generate_unique_secure_token(attribute) }
      end

      def generate_unique_secure_token(attribute)
        (1..10).detect { SecureRandom.hex(12) unless exists?(attribute => random_token) } ||
          raise "Couldn't generate a unique token in 10 attempts!"
      end

(All untested code that I just mocked out).

Member

dhh commented Dec 27, 2014

Here's another stab at a version that includes collision checking/mitigation:

      def has_secure_token(attribute)
        require 'securerandom'
        define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) }
        before_create { self[attribute] = self.class.generate_unique_secure_token(attribute) }
      end

      def generate_unique_secure_token(attribute)
        (1..10).detect { SecureRandom.hex(12) unless exists?(attribute => random_token) } ||
          raise "Couldn't generate a unique token in 10 attempts!"
      end

(All untested code that I just mocked out).

@simi

This comment has been minimized.

Show comment
Hide comment
@simi

simi Dec 27, 2014

Contributor

I did a little experiment with SecureRandom.hex collision using https://gist.github.com/simi/d041f95d2e6a6b8ea2f7.

On Windows there wasn't collision in 100_000_000 iterations with 24 key length.
On Linux there wasn't collision in 10_000_000 iterations with 24 key length.
I reduced iterations on Linux since I owe a crappy linux machine.

But it is still random and collision can happen anytime. 馃槥

Contributor

simi commented Dec 27, 2014

I did a little experiment with SecureRandom.hex collision using https://gist.github.com/simi/d041f95d2e6a6b8ea2f7.

On Windows there wasn't collision in 100_000_000 iterations with 24 key length.
On Linux there wasn't collision in 10_000_000 iterations with 24 key length.
I reduced iterations on Linux since I owe a crappy linux machine.

But it is still random and collision can happen anytime. 馃槥

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 27, 2014

Member

Yeah, I think we鈥檒l just deal with it as proposed in the second code example I shared. This should be plug鈥檔鈥檉orget.

On Dec 27, 2014, at 10:17 AM, Josef 艩im谩nek notifications@github.com wrote:

I did a little experiment with SecureRandom.hex collision using https://gist.github.com/simi/d041f95d2e6a6b8ea2f7 https://gist.github.com/simi/d041f95d2e6a6b8ea2f7.

On Windows there wasn't collision in 100_000_000 iterations with 24 key length.
On Linux there wasn't collision in 10_000_000 iterations with 24 key length.
I reduced iterations on Linux since I owe a crappy linux machine.

But it is still random and collision can happen anytime.


Reply to this email directly or view it on GitHub #18217 (comment).

Member

dhh commented Dec 27, 2014

Yeah, I think we鈥檒l just deal with it as proposed in the second code example I shared. This should be plug鈥檔鈥檉orget.

On Dec 27, 2014, at 10:17 AM, Josef 艩im谩nek notifications@github.com wrote:

I did a little experiment with SecureRandom.hex collision using https://gist.github.com/simi/d041f95d2e6a6b8ea2f7 https://gist.github.com/simi/d041f95d2e6a6b8ea2f7.

On Windows there wasn't collision in 100_000_000 iterations with 24 key length.
On Linux there wasn't collision in 10_000_000 iterations with 24 key length.
I reduced iterations on Linux since I owe a crappy linux machine.

But it is still random and collision can happen anytime.


Reply to this email directly or view it on GitHub #18217 (comment).

@robertomiranda robertomiranda changed the title from Add has_secure_token to Active Model to Add has_secure_token to Active Record Dec 27, 2014

@robertomiranda

This comment has been minimized.

Show comment
Hide comment
@robertomiranda

robertomiranda Dec 27, 2014

Contributor

Guys I moved this to AR and keeping guideline of the second @dhh's code example

Contributor

robertomiranda commented Dec 27, 2014

Guys I moved this to AR and keeping guideline of the second @dhh's code example

Show outdated Hide outdated activerecord/lib/active_record/secure_token.rb
random_token = SecureRandom.hex(12)
return random_token unless exists?(attribute => random_token)
end
raise "Couldn't generate a unique token in 10 attempts!"

This comment has been minimized.

@robertomiranda

robertomiranda Dec 27, 2014

Contributor

not sure if we should have a custom exception here wdyt?

@robertomiranda

robertomiranda Dec 27, 2014

Contributor

not sure if we should have a custom exception here wdyt?

This comment has been minimized.

@robertomiranda

robertomiranda Dec 27, 2014

Contributor

something like raise CollisionLimitReached, "Couldn't generate a unique token in 10 attempts!"?

@robertomiranda

robertomiranda Dec 27, 2014

Contributor

something like raise CollisionLimitReached, "Couldn't generate a unique token in 10 attempts!"?

This comment has been minimized.

@dhh

dhh Dec 27, 2014

Member

Don't think we need a custom exception. This is extremely unlikely to happen, so not something anyone would custom catch anyway.

@dhh

dhh Dec 27, 2014

Member

Don't think we need a custom exception. This is extremely unlikely to happen, so not something anyone would custom catch anyway.

This comment has been minimized.

@dhh

dhh Dec 27, 2014

Member

I don't like this return / raise flow, though. What was wrong with the detect approach?

@dhh

dhh Dec 27, 2014

Member

I don't like this return / raise flow, though. What was wrong with the detect approach?

This comment has been minimized.

@robertomiranda

robertomiranda Dec 28, 2014

Contributor

The issue with detect is that this methods finds the element on an enumerable, so basically in most of the cases will returns 1 or 2 instead of the token needed

@robertomiranda

robertomiranda Dec 28, 2014

Contributor

The issue with detect is that this methods finds the element on an enumerable, so basically in most of the cases will returns 1 or 2 instead of the token needed

This comment has been minimized.

@robertomiranda

robertomiranda Dec 28, 2014

Contributor

maybe another approach more clear would be

10.times do
  if random_token = SecureRandom.hex(12) && exists?(attribute => random_token)
    raise "Couldn't generate a unique token in 10 attempts!"
  else
    return random_token      
  end
end
@robertomiranda

robertomiranda Dec 28, 2014

Contributor

maybe another approach more clear would be

10.times do
  if random_token = SecureRandom.hex(12) && exists?(attribute => random_token)
    raise "Couldn't generate a unique token in 10 attempts!"
  else
    return random_token      
  end
end

This comment has been minimized.

@dhh

dhh Dec 28, 2014

Member

Ah, right, here's a variation that might work:

10.times do |i|
  SecureRandom.hex(12).tap do |token|
    if exists?(attribute => token) && i == 9
      raise "Couldn't generate a unique token in 10 attempts!"
    else
      return token
    end
  end
end

Don't love that the exceptional state is the first conditional, but ok.

@dhh

dhh Dec 28, 2014

Member

Ah, right, here's a variation that might work:

10.times do |i|
  SecureRandom.hex(12).tap do |token|
    if exists?(attribute => token) && i == 9
      raise "Couldn't generate a unique token in 10 attempts!"
    else
      return token
    end
  end
end

Don't love that the exceptional state is the first conditional, but ok.

This comment has been minimized.

@robertomiranda

robertomiranda Dec 28, 2014

Contributor

Done 馃憤

@robertomiranda

robertomiranda Dec 28, 2014

Contributor

Done 馃憤

This comment has been minimized.

@dhh

dhh Dec 28, 2014

Member

This isn't right. This will raise an exception on the first collision. It's supposed to not raise an exception until the 10th time it still produces a collision.

On Dec 28, 2014, at 09:47, Roberto Miranda notifications@github.com wrote:

In activerecord/lib/active_record/secure_token.rb:

  •  #   user.save
    
  •  #   user.token # => "44539a6a59835a4ee9d7b112"
    
  •  #   user.regenerate_token # => true
    
  •  def has_secure_token(attribute = :token)
    
  •    # Load securerandom only when has_secure_key is used.
    
  •    define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) }
    
  •    before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token(attribute)) }
    
  •  end
    
  •  def generate_unique_secure_token(attribute)
    
  •    require 'securerandom'
    
  •    10.times do
    
  •      random_token = SecureRandom.hex(12)
    
  •      return random_token unless exists?(attribute => random_token)
    
  •    end
    
  •    raise "Couldn't generate a unique token in 10 attempts!"
    
    Done


Reply to this email directly or view it on GitHub.

@dhh

dhh Dec 28, 2014

Member

This isn't right. This will raise an exception on the first collision. It's supposed to not raise an exception until the 10th time it still produces a collision.

On Dec 28, 2014, at 09:47, Roberto Miranda notifications@github.com wrote:

In activerecord/lib/active_record/secure_token.rb:

  •  #   user.save
    
  •  #   user.token # => "44539a6a59835a4ee9d7b112"
    
  •  #   user.regenerate_token # => true
    
  •  def has_secure_token(attribute = :token)
    
  •    # Load securerandom only when has_secure_key is used.
    
  •    define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) }
    
  •    before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token(attribute)) }
    
  •  end
    
  •  def generate_unique_secure_token(attribute)
    
  •    require 'securerandom'
    
  •    10.times do
    
  •      random_token = SecureRandom.hex(12)
    
  •      return random_token unless exists?(attribute => random_token)
    
  •    end
    
  •    raise "Couldn't generate a unique token in 10 attempts!"
    
    Done


Reply to this email directly or view it on GitHub.

Show outdated Hide outdated activerecord/lib/active_record/secure_token.rb
10.times do |i|
SecureRandom.hex(12).tap do |token|
if exists?(attribute => token)
raise "Couldn't generate a unique token in 10 attempts!"

This comment has been minimized.

@simi

simi Dec 28, 2014

Contributor

This can raise on every iteration.

What about?

raise "Couldn't generate a unique token in 10 attempts!" if i == 9
@simi

simi Dec 28, 2014

Contributor

This can raise on every iteration.

What about?

raise "Couldn't generate a unique token in 10 attempts!" if i == 9
Show outdated Hide outdated activerecord/test/cases/secure_token_test.rb
def test_raise_and_exception_when_there_is_a_collision
User.stubs(:exists?).returns(true)
assert_raises(RuntimeError) do

This comment has been minimized.

@robertomiranda

robertomiranda Dec 28, 2014

Contributor

fixed the issue of each iterations that raise an exception, but not sure with change this test is failing, seems to be everything covered. I was debugging into the test case code and 10.times only makes to iterations and is always returning 2 through the variable i, really weird

@robertomiranda

robertomiranda Dec 28, 2014

Contributor

fixed the issue of each iterations that raise an exception, but not sure with change this test is failing, seems to be everything covered. I was debugging into the test case code and 10.times only makes to iterations and is always returning 2 through the variable i, really weird

This comment has been minimized.

@dhh

dhh Dec 28, 2014

Member

Not sure I understand? We should have a test case that covers exists? returning true, say, three times, and then returns false to cover the retry action.

I think you can use User.expects(:exists?).times(3).returns(true); User.expects(:exists?).returns(false) or something like that?

@dhh

dhh Dec 28, 2014

Member

Not sure I understand? We should have a test case that covers exists? returning true, say, three times, and then returns false to cover the retry action.

I think you can use User.expects(:exists?).times(3).returns(true); User.expects(:exists?).returns(false) or something like that?

This comment has been minimized.

@simi

simi Dec 28, 2014

Contributor
Show outdated Hide outdated activemodel/test/models/user.rb
@@ -1,7 +1,7 @@
class User
extend ActiveModel::Callbacks
include ActiveModel::SecurePassword

This comment has been minimized.

@dhh

dhh Dec 28, 2014

Member

Let's remove this unrelated change from the PR.

@dhh

dhh Dec 28, 2014

Member

Let's remove this unrelated change from the PR.

Show outdated Hide outdated activerecord/test/cases/secure_token_test.rb
@user = User.new
end
def test_assing_unique_token_values

This comment has been minimized.

@dhh

dhh Dec 28, 2014

Member

Let's use the common test "" do/end format for the cases.

@dhh

dhh Dec 28, 2014

Member

Let's use the common test "" do/end format for the cases.

This comment has been minimized.

@simi

simi Jan 5, 2015

Contributor

Is this preferred way over def test?

@simi

simi Jan 5, 2015

Contributor

Is this preferred way over def test?

Show outdated Hide outdated activerecord/test/cases/secure_token_test.rb
require 'models/user'
class SecureTokenTest < ActiveRecord::TestCase
def setup

This comment has been minimized.

@dhh

dhh Dec 28, 2014

Member

setup do/end here as well.

@dhh

dhh Dec 28, 2014

Member

setup do/end here as well.

Show outdated Hide outdated activerecord/test/cases/secure_token_test.rb
assert_not_nil @user.auth_token
end
def test_default_length_of_secure_token_is_set_to_24

This comment has been minimized.

@dhh

dhh Dec 28, 2014

Member

It's not a default length any more in the sense that it can be changed, so this test case imo doesn't add anything over the preceding one.

@dhh

dhh Dec 28, 2014

Member

It's not a default length any more in the sense that it can be changed, so this test case imo doesn't add anything over the preceding one.

Show outdated Hide outdated activerecord/test/cases/secure_token_test.rb
@user.save
old_token = @user.token
old_auth_token = @user.auth_token
assert @user.regenerate_token

This comment has been minimized.

@dhh

dhh Dec 28, 2014

Member

Don't need to assert this call. We're not making any contract claims on the return value.

@dhh

dhh Dec 28, 2014

Member

Don't need to assert this call. We're not making any contract claims on the return value.

Show outdated Hide outdated activerecord/test/cases/secure_token_test.rb
assert_not_equal @user.token, old_token
assert_not_equal @user.auth_token, old_auth_token
assert_equal 24, @user.token.length

This comment has been minimized.

@dhh

dhh Dec 28, 2014

Member

Don't need to test for the length.

@dhh

dhh Dec 28, 2014

Member

Don't need to test for the length.

Show outdated Hide outdated activerecord/lib/active_record/secure_token.rb
def generate_unique_secure_token(attribute)
10.times do |i|
SecureRandom.hex(12).tap do |token|
if exists?(attribute => token) && i == 9

This comment has been minimized.

@simi

simi Dec 28, 2014

Contributor

Actually this is not good also. i == 9 should be around raise statement. Now it will raise only if last iteration will produce existing token, but only one iteration will be done, since first token will be returned, because this statement will be false.

This is also reason for that failing test.

@simi

simi Dec 28, 2014

Contributor

Actually this is not good also. i == 9 should be around raise statement. Now it will raise only if last iteration will produce existing token, but only one iteration will be done, since first token will be returned, because this statement will be false.

This is also reason for that failing test.

This comment has been minimized.

@dhh

dhh Dec 28, 2014

Member

You鈥檙e right, Josef. Good catch!

On Dec 28, 2014, at 12:38 PM, Josef 艩im谩nek notifications@github.com wrote:

In activerecord/lib/active_record/secure_token.rb #18217 (diff):

  •  #
    
  •  #   user = User.new
    
  •  #   user.save
    
  •  #   user.token # => "44539a6a59835a4ee9d7b112"
    
  •  #   user.regenerate_token # => true
    
  •  def has_secure_token(attribute = :token)
    
  •    # Load securerandom only when has_secure_key is used.
    
  •    require 'securerandom'
    
  •    define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) }
    
  •    before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token(attribute)) }
    
  •  end
    
  •  def generate_unique_secure_token(attribute)
    
  •    10.times do |i|
    
  •      SecureRandom.hex(12).tap do |token|
    
  •        if exists?(attribute => token) && i == 9
    
    Actually this is not good also. i == 9 should be around raise statement. Now it will raise only if last iteration will produce existing token, but only one iteration will be done, since first token will be returned, because this statement will be false.

This is also reason for that failing test.


Reply to this email directly or view it on GitHub https://github.com/rails/rails/pull/18217/files#r22300209.

@dhh

dhh Dec 28, 2014

Member

You鈥檙e right, Josef. Good catch!

On Dec 28, 2014, at 12:38 PM, Josef 艩im谩nek notifications@github.com wrote:

In activerecord/lib/active_record/secure_token.rb #18217 (diff):

  •  #
    
  •  #   user = User.new
    
  •  #   user.save
    
  •  #   user.token # => "44539a6a59835a4ee9d7b112"
    
  •  #   user.regenerate_token # => true
    
  •  def has_secure_token(attribute = :token)
    
  •    # Load securerandom only when has_secure_key is used.
    
  •    require 'securerandom'
    
  •    define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) }
    
  •    before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token(attribute)) }
    
  •  end
    
  •  def generate_unique_secure_token(attribute)
    
  •    10.times do |i|
    
  •      SecureRandom.hex(12).tap do |token|
    
  •        if exists?(attribute => token) && i == 9
    
    Actually this is not good also. i == 9 should be around raise statement. Now it will raise only if last iteration will produce existing token, but only one iteration will be done, since first token will be returned, because this statement will be false.

This is also reason for that failing test.


Reply to this email directly or view it on GitHub https://github.com/rails/rails/pull/18217/files#r22300209.

Show outdated Hide outdated activerecord/lib/active_record/secure_token.rb
module ClassMethods
# Example using has_secure_token
#
# # Schema: User(auth_token:string, invitation_token:string)

This comment has been minimized.

@robin850

robin850 Dec 29, 2014

Member

Well, that's a tiny documentation concern but I guess it should rather be something like Schema: User(token:string, auth_token:string) and then in the example model you can show the two paths (the default one and the one passing an argument to the method):

#   class User < ActiveRecord::Base
#     has_secure_token
#     has_secure_token :auth_token
#   end

Otherwise pretty nice patch! 馃憤

@robin850

robin850 Dec 29, 2014

Member

Well, that's a tiny documentation concern but I guess it should rather be something like Schema: User(token:string, auth_token:string) and then in the example model you can show the two paths (the default one and the one passing an argument to the method):

#   class User < ActiveRecord::Base
#     has_secure_token
#     has_secure_token :auth_token
#   end

Otherwise pretty nice patch! 馃憤

This comment has been minimized.

@robertomiranda

robertomiranda Dec 29, 2014

Contributor

@robin850 thanks and done 馃憤

@robertomiranda

robertomiranda Dec 29, 2014

Contributor

@robin850 thanks and done 馃憤

def generate_unique_secure_token(attribute)
10.times do |i|
SecureRandom.hex(12).tap do |token|
if exists?(attribute => token)

This comment has been minimized.

@tenderlove

tenderlove Dec 29, 2014

Member

Why are we bothering with exists?? There is still a race condition. We can't guarantee the uniqueness until it actually inserts in to the database.

@tenderlove

tenderlove Dec 29, 2014

Member

Why are we bothering with exists?? There is still a race condition. We can't guarantee the uniqueness until it actually inserts in to the database.

This comment has been minimized.

@dhh

dhh Dec 29, 2014

Member

Decrease the number of times that uniqueness exception will be thrown. Similar to the validates_presence_of case. There's still a race condition, but we work to minimize the likelihood that an exception needs to bubble up to the user.

@dhh

dhh Dec 29, 2014

Member

Decrease the number of times that uniqueness exception will be thrown. Similar to the validates_presence_of case. There's still a race condition, but we work to minimize the likelihood that an exception needs to bubble up to the user.

This comment has been minimized.

@tenderlove

tenderlove Dec 29, 2014

Member

Isn't this just wasteful most of the time though?

@tenderlove

tenderlove Dec 29, 2014

Member

Isn't this just wasteful most of the time though?

This comment has been minimized.

@tenderlove

tenderlove Dec 29, 2014

Member

Also, shouldn't we go to 11? Wouldn't that decrease the chances even further?

@tenderlove

tenderlove Dec 29, 2014

Member

Also, shouldn't we go to 11? Wouldn't that decrease the chances even further?

This comment has been minimized.

@dhh

dhh Dec 29, 2014

Member

When there's not a collision (most of the time), it'll return on the first loop. So only 1 exists? call for the majority case. That seems legit enough.

Heh. I don't think the iteration count really matters. I'd be fine to TURN IT UP TO 11 just for the fun of it ;)

@dhh

dhh Dec 29, 2014

Member

When there's not a collision (most of the time), it'll return on the first loop. So only 1 exists? call for the majority case. That seems legit enough.

Heh. I don't think the iteration count really matters. I'd be fine to TURN IT UP TO 11 just for the fun of it ;)

This comment has been minimized.

@chancancode

chancancode Jan 4, 2015

Member

cc @dhh, what do you think about adding this to the migration generator?

@chancancode

chancancode Jan 4, 2015

Member

cc @dhh, what do you think about adding this to the migration generator?

This comment has been minimized.

@dhh

dhh Jan 4, 2015

Member

Me likey 馃憤 鈥 and sounds good to drop the retry with base62.

@dhh

dhh Jan 4, 2015

Member

Me likey 馃憤 鈥 and sounds good to drop the retry with base62.

This comment has been minimized.

@chancancode

chancancode Jan 4, 2015

Member

Great 馃榿 @robertomiranda are you still interested in pursuing these?

@chancancode

chancancode Jan 4, 2015

Member

Great 馃榿 @robertomiranda are you still interested in pursuing these?

This comment has been minimized.

@robertomiranda

robertomiranda Jan 5, 2015

Contributor

@chancancode yes, I am. I think that the path to take here is:

  1. Add Base62 monkey patch to ActiveSupport
  2. Switch to Base62 for generates secure tokens and remove the retry
  3. Add the secure tokens migration helper and generator
  4. Move SecureToken to ActiveModel, in favor of "plug and play"
@robertomiranda

robertomiranda Jan 5, 2015

Contributor

@chancancode yes, I am. I think that the path to take here is:

  1. Add Base62 monkey patch to ActiveSupport
  2. Switch to Base62 for generates secure tokens and remove the retry
  3. Add the secure tokens migration helper and generator
  4. Move SecureToken to ActiveModel, in favor of "plug and play"

This comment has been minimized.

@chabgood

chabgood Mar 21, 2016

By "it adds a unique constraint" is this an index with a unique constraint on that column?

@chabgood

chabgood Mar 21, 2016

By "it adds a unique constraint" is this an index with a unique constraint on that column?

def generate_unique_secure_token(attribute)
10.times do |i|
SecureRandom.hex(12).tap do |token|

This comment has been minimized.

@tenderlove

tenderlove Dec 29, 2014

Member

Do we need to use hex? How about uuid? It would work nicely with PG.

@tenderlove

tenderlove Dec 29, 2014

Member

Do we need to use hex? How about uuid? It would work nicely with PG.

This comment has been minimized.

@dhh

dhh Dec 29, 2014

Member

uuid is 36 characters and uses dashes. We were looking for a format that was alphanumeric only and shorter.

@dhh

dhh Dec 29, 2014

Member

uuid is 36 characters and uses dashes. We were looking for a format that was alphanumeric only and shorter.

This comment has been minimized.

@matthewd

matthewd Dec 29, 2014

Member

I thought we'd settled on base62, in order to achieve a sufficient keyspace while keeping the string optimally short.

@matthewd

matthewd Dec 29, 2014

Member

I thought we'd settled on base62, in order to achieve a sufficient keyspace while keeping the string optimally short.

This comment has been minimized.

@dhh

dhh Dec 29, 2014

Member

I vaguely remember something about that. Can you recap the conversation? What was the benefit of base62 over hex(12)?

@dhh

dhh Dec 29, 2014

Member

I vaguely remember something about that. Can you recap the conversation? What was the benefit of base62 over hex(12)?

This comment has been minimized.

@matthewd

matthewd Dec 29, 2014

Member

For a given length of 24:

16 ** 24  # => 79228162514264337593543950336
62 ** 24  # => 10408797222153426578715765348940396820955136

and apart from needing slightly more code to generate it, it's basically free.

The discussion went a bit into the woods because base62-the-algorithm is complicated, but we don't care: we just want to generate a series of characters using base62-the-charset.

@matthewd

matthewd Dec 29, 2014

Member

For a given length of 24:

16 ** 24  # => 79228162514264337593543950336
62 ** 24  # => 10408797222153426578715765348940396820955136

and apart from needing slightly more code to generate it, it's basically free.

The discussion went a bit into the woods because base62-the-algorithm is complicated, but we don't care: we just want to generate a series of characters using base62-the-charset.

This comment has been minimized.

@dhh

dhh Dec 29, 2014

Member

Ah, I see. So even less chance of a collision. Gotcha. Sounds good to me 馃憤. Could happen in a separate PR though.

On Dec 29, 2014, at 15:13, Matthew Draper notifications@github.com wrote:

In activerecord/lib/active_record/secure_token.rb:

  •  #   end
    
  •  #
    
  •  #   user = User.new
    
  •  #   user.save
    
  •  #   user.token # => "44539a6a59835a4ee9d7b112"
    
  •  #   user.regenerate_token # => true
    
  •  def has_secure_token(attribute = :token)
    
  •    # Load securerandom only when has_secure_key is used.
    
  •    require 'securerandom'
    
  •    define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) }
    
  •    before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token(attribute)) }
    
  •  end
    
  •  def generate_unique_secure_token(attribute)
    
  •    10.times do |i|
    
  •      SecureRandom.hex(12).tap do |token|
    

For a given length of 24:

16 ** 24 # => 79228162514264337593543950336
62 ** 24 # => 10408797222153426578715765348940396820955136

and apart from needing slightly more code to generate it, it's basically free.

The discussion went a bit into the woods because base62-the-algorithm is complicated, but we don't care: we just want to generate a series of characters using base62-the-charset.


Reply to this email directly or view it on GitHub.

@dhh

dhh Dec 29, 2014

Member

Ah, I see. So even less chance of a collision. Gotcha. Sounds good to me 馃憤. Could happen in a separate PR though.

On Dec 29, 2014, at 15:13, Matthew Draper notifications@github.com wrote:

In activerecord/lib/active_record/secure_token.rb:

  •  #   end
    
  •  #
    
  •  #   user = User.new
    
  •  #   user.save
    
  •  #   user.token # => "44539a6a59835a4ee9d7b112"
    
  •  #   user.regenerate_token # => true
    
  •  def has_secure_token(attribute = :token)
    
  •    # Load securerandom only when has_secure_key is used.
    
  •    require 'securerandom'
    
  •    define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) }
    
  •    before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token(attribute)) }
    
  •  end
    
  •  def generate_unique_secure_token(attribute)
    
  •    10.times do |i|
    
  •      SecureRandom.hex(12).tap do |token|
    

For a given length of 24:

16 ** 24 # => 79228162514264337593543950336
62 ** 24 # => 10408797222153426578715765348940396820955136

and apart from needing slightly more code to generate it, it's basically free.

The discussion went a bit into the woods because base62-the-algorithm is complicated, but we don't care: we just want to generate a series of characters using base62-the-charset.


Reply to this email directly or view it on GitHub.

Show outdated Hide outdated activerecord/lib/active_record/secure_token.rb
#
# SecureRandom is used to generate the 24-character unique token, so collisions are highly unlikely.
# But you're still encouraged to enforce uniqueness, if that's required, by something like a unique index
# in the database or through a validation check.

This comment has been minimized.

@dhh

dhh Dec 29, 2014

Member

Updated comment:

SecureRandom is used to generate the 24-character unique token, so collisions are highly unlikely. We'll check to see if the generated token has been used already using #exists?, and retry up to 10 times to find another unused token. After that a RuntimeError is raised if the problem persists.

Note that it's still possible to generate a race condition in the database in the same way that validates_presence_of can. You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.

@dhh

dhh Dec 29, 2014

Member

Updated comment:

SecureRandom is used to generate the 24-character unique token, so collisions are highly unlikely. We'll check to see if the generated token has been used already using #exists?, and retry up to 10 times to find another unused token. After that a RuntimeError is raised if the problem persists.

Note that it's still possible to generate a race condition in the database in the same way that validates_presence_of can. You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.

This comment has been minimized.

@robertomiranda

robertomiranda Dec 30, 2014

Contributor

done!

@robertomiranda

robertomiranda Dec 30, 2014

Contributor

done!

Show outdated Hide outdated activerecord/test/cases/secure_token_test.rb
assert_not_equal @user.auth_token, old_auth_token
end
test "raise and exception when there's a collision" do

This comment has been minimized.

@dhh

dhh Dec 30, 2014

Member

We still need another test case for "9 collisions, then a success = win" and "10 collisions exactly = fail" to test that the main loop is functioning as it should.

@dhh

dhh Dec 30, 2014

Member

We still need another test case for "9 collisions, then a success = win" and "10 collisions exactly = fail" to test that the main loop is functioning as it should.

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Dec 30, 2014

Member

Almost there. Please squish this once you got the final test cases done.

And then let's start another PR to get the base62 upgrade done as well.

Member

dhh commented Dec 30, 2014

Almost there. Please squish this once you got the final test cases done.

And then let's start another PR to get the base62 upgrade done as well.

@robin850 robin850 added this to the 5.0.0 milestone Dec 30, 2014

@hundredwatt

This comment has been minimized.

Show comment
Hide comment
@hundredwatt

hundredwatt Jan 1, 2015

Devise recently moved from storing tokens directly to storing an HMAC digest of the token: plataformatec/devise@143794d

From a security perspective, should that be considered here?

Devise recently moved from storing tokens directly to storing an HMAC digest of the token: plataformatec/devise@143794d

From a security perspective, should that be considered here?

Add has_secure_token to Active Record
Update SecureToken Docs

Add Changelog entry for has_secure_token [ci skip]
end
end
test "assing unique token after 9 attemps reached" do

This comment has been minimized.

@robertomiranda

robertomiranda Jan 4, 2015

Contributor

@dhh: We still need another test case for "9 collisions, then a success = win" and "10 collisions exactly = fail" to test that the main loop is functioning as it should.

done!

@robertomiranda

robertomiranda Jan 4, 2015

Contributor

@dhh: We still need another test case for "9 collisions, then a success = win" and "10 collisions exactly = fail" to test that the main loop is functioning as it should.

done!

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Jan 4, 2015

Member

This is looking good to me now 馃憤. I say we still want to do the base62 upgrade, but we can do that after merging.

Member

dhh commented Jan 4, 2015

This is looking good to me now 馃憤. I say we still want to do the base62 upgrade, but we can do that after merging.

@robertomiranda

This comment has been minimized.

Show comment
Hide comment
@robertomiranda

robertomiranda Jan 4, 2015

Contributor

@dhh great, once this PR be already merged I'll open another one with base62 upgrade 馃憤

Contributor

robertomiranda commented Jan 4, 2015

@dhh great, once this PR be already merged I'll open another one with base62 upgrade 馃憤

dhh added a commit that referenced this pull request Jan 4, 2015

@dhh dhh merged commit 33a13c9 into rails:master Jan 4, 2015

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Jan 4, 2015

Member

There you go :)

Member

dhh commented Jan 4, 2015

There you go :)

@simi

This comment has been minimized.

Show comment
Hide comment
@simi

simi Jan 4, 2015

Contributor

馃憦

Contributor

simi commented Jan 4, 2015

馃憦

@jonatack

This comment has been minimized.

Show comment
Hide comment
@jonatack

jonatack Jan 5, 2015

Contributor

馃帀

Contributor

jonatack commented Jan 5, 2015

馃帀

# validates_presence_of can. You're encouraged to add a unique index in the database to deal with
# this even more unlikely scenario.
def has_secure_token(attribute = :token)
# Load securerandom only when has_secure_key is used.

This comment has been minimized.

@aripollak

aripollak Jan 10, 2015

Contributor

s/key/token/?

@aripollak

aripollak Jan 10, 2015

Contributor

s/key/token/?

@aripollak

This comment has been minimized.

Show comment
Hide comment
@aripollak

aripollak Jan 10, 2015

Contributor

It seems like @hundredwatt's comment wasn't addressed here. It seems weird to me to call something a secure token if it's stored in the database as plaintext, when the equivalent for passwords, has_secure_password, stores its value encrypted.

Contributor

aripollak commented Jan 10, 2015

It seems like @hundredwatt's comment wasn't addressed here. It seems weird to me to call something a secure token if it's stored in the database as plaintext, when the equivalent for passwords, has_secure_password, stores its value encrypted.

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Jan 10, 2015

Member

The security is in terms of its random generation. See SecureRandom.

On Jan 9, 2015, at 9:45 PM, Ari Pollak notifications@github.com wrote:

It seems like @hundredwatt https://github.com/hundredwatt's comment wasn't addressed here. It seems weird to me to call something a secure token if it's stored in the database as plaintext, when the equivalent for passwords, has_secure_password, stores its value encrypted.


Reply to this email directly or view it on GitHub #18217 (comment).

Member

dhh commented Jan 10, 2015

The security is in terms of its random generation. See SecureRandom.

On Jan 9, 2015, at 9:45 PM, Ari Pollak notifications@github.com wrote:

It seems like @hundredwatt https://github.com/hundredwatt's comment wasn't addressed here. It seems weird to me to call something a secure token if it's stored in the database as plaintext, when the equivalent for passwords, has_secure_password, stores its value encrypted.


Reply to this email directly or view it on GitHub #18217 (comment).

module ClassMethods
# Example using has_secure_token
#
# # Schema: User(toke:string, auth_token:string)

This comment has been minimized.

@abscondite

abscondite Feb 8, 2016

missed 'n'

This comment has been minimized.

@maclover7

maclover7 Feb 9, 2016

Member

Fixed via #18537.

@maclover7

maclover7 Feb 9, 2016

Member

Fixed via #18537.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment