Rails 6.1 adds support for signed ids to Active Record


There are many ways of generating a signed link for implementing things like invitation email, unsubscribe link or password reset feature. You could add a token field on the model, use JWT tokens. But in the upcoming Rails versions, the functionality to generate tamper-proof and verifiable ids will be built into rails.

# user = User.find(1)
# signed_user_id = user.signed_id

# User.find_signed signed_user_id
=> [user record]

# User.find_signed invalid_data
=> ActiveSupport::MessageVerifier::InvalidSignature

The secret to encode and decode messages is auto generated using ActiveSupport::KeyGenerator. You can also provide your own secret key by setting signed_id_verifier_secret on ActiveRecord::Base inside of an initializer.

The signed ids could also be configured to expire. There is also support for generating ids with specific purpose like password reset.

# user = User.find(1)
# signed_user_id = expires_in: 15.minutes, purpose: :password_reset

The signed ids are generated and verified using ActiveSupport::MessageVerifier which creates HMAC signarutes as Base64-encoded string.

The feature will be available when Rails 6.1 is released. If you want to start using it right away, then it can be implemented as a model concern using ActiveSupport::MessageVerifier

module SignedIds
  extend ActiveSupport::Concern

  def signed_id
    self.class.signed_id_verifier.generate id
  end

  private

  class_methods do

    def find_signed(signed_id)
      if id = signed_id_verifier.verified(signed_id)
        find(id)
      end
    end

    def signed_id_verifier
      ActiveSupport::MessageVerifier.new signed_id_verifier_secret digest: 'SHA256'
    end

    def signed_id_verifier_secret
      @_signed_id_verifier_secret ||= Rails.application.config.signed_id_verifier_secret
    end

  end

end