Improve ActiveSupport::MessageVerifier and ActiveRecord::SignedId docs

The documentation on ActiveSupport::MessageVerifier used the “sensitive data” string as an example; that wording might induce the developer to think we’re dealing with encryption, while the payload is actually only Base64 encoded and is not protected at all.

We also improve the documentation on ActiveRecord::SignedId, which uses MessageVerifier and thereby will also expose the ID as encoded cleartext, making explicit that it’s not encryption, only signing.

Lastly, we refer the developer to MessageEncryptor if the payload needs to be encrypted.
This commit is contained in:
Felipe 2024-06-08 19:36:09 -03:00 committed by Rafael Mendonça França
parent 9fdaea5215
commit ad20d9e7ec
No known key found for this signature in database
GPG Key ID: FC23B6D0F1EEE948
3 changed files with 29 additions and 5 deletions

@ -106,7 +106,16 @@ def combine_signed_id_purposes(purpose)
# Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
#
# This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
# However, as with any message signed with a +ActiveSupport::MessageVerifier+,
# {the signed id is not encrypted}[link:classes/ActiveSupport/MessageVerifier.html#class-ActiveSupport::MessageVerifier-label-Signing+is+not+encryption].
# It's just encoded and protected against tampering.
#
# This means that the ID can be decoded by anyone; however, if tampered with (so to point to a different ID),
# the cryptographic signature will no longer match, and the signed id will be considered invalid and return nil
# when passed to +find_signed+ (or raise with +find_signed!+).
#
# It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
# If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
# record. If a purpose is set, this too must match.

@ -30,6 +30,18 @@ module ActiveSupport
# self.current_user = User.find(id)
# end
#
# === Signing is not encryption
#
# The signed messages are not encrypted. The payload is merely encoded (Base64 by default) and can be decoded by
# anyone. The signature is just assuring that the message wasn't tampered with. For example:
#
# message = Rails.application.message_verifier('my_purpose').generate('never put secrets here')
# # => "BAhJIhtuZXZlciBwdXQgc2VjcmV0cyBoZXJlBjoGRVQ=--a0c1c0827919da5e949e989c971249355735e140"
# Base64.decode64(message.split("--").first) # no key needed
# # => 'never put secrets here'
#
# If you also need to encrypt the contents, you must use ActiveSupport::MessageEncryptor instead.
#
# === Confine messages to a specific purpose
#
# It's not recommended to use the same verifier for different purposes in your application.

@ -212,17 +212,20 @@ def message_verifiers
# It is recommended not to use the same verifier for different things, so you can get different
# verifiers passing the +verifier_name+ argument.
#
# For instance, +ActiveStorage::Blob.signed_id_verifier+ is implemented using this feature, which assures that
# the IDs strings haven't been tampered with and are safe to use in a finder.
#
# See the ActiveSupport::MessageVerifier documentation for more information.
#
# ==== Parameters
#
# * +verifier_name+ - the name of the message verifier.
#
# ==== Examples
#
# message = Rails.application.message_verifier('sensitive_data').generate('my sensible data')
# Rails.application.message_verifier('sensitive_data').verify(message)
# # => 'my sensible data'
#
# See the ActiveSupport::MessageVerifier documentation for more information.
# message = Rails.application.message_verifier('my_purpose').generate('data to sign against tampering')
# Rails.application.message_verifier('my_purpose').verify(message)
# # => 'data to sign against tampering'
def message_verifier(verifier_name)
message_verifiers[verifier_name]
end