class ActiveSupport::MessageVerifier

verifier.rotate(old_secret, digest: “SHA256”, serializer: Marshal)
Though the above would most likely be combined into one rotation:
verifier.rotate(serializer: Marshal) # Fallback to an old serializer instead of JSON.
verifier.rotate(digest: “SHA256”) # Fallback to an old digest instead of SHA512.
verifier.rotate(old_secret) # Fallback to an old secret instead of @secret.
generated with the old values will then work until the rotation is removed.
Then gradually rotate the old values out by adding them as fallbacks. Any message
verifier = ActiveSupport::MessageVerifier.new(@secret, digest: “SHA512”, serializer: JSON)
You’d give your verifier the new defaults:
verifier unless specified otherwise.
By default any rotated verifiers use the values of the primary
either verified or verify will also try verifying with the fallback.
back to a stack of verifiers. Call rotate to build and add a verifier so
MessageVerifier also supports rotating out old configurations by falling
=== Rotating keys
ActiveSupport::MessageVerifier::InvalidSignature.
Thereafter, the verified method returns nil while verify raises
Messages can then be verified and returned until expiry.
@verifier.generate(“signed message”, expires_at: Time.now.end_of_year)
@verifier.generate(“signed message”, expires_in: 1.month)
time with :expires_in or :expires_at.
return the original value. But messages can be set to expire at a given
By default messages last forever and verifying one year from now will still
=== Expiring messages
@verifier.verify(token) # => “signed message”
@verifier.verify(token, purpose: :redirect) # => raises ActiveSupport::MessageVerifier::InvalidSignature
@verifier.verified(token) # => “signed message”
@verifier.verified(token, purpose: :redirect) # => nil
token = @verifier.generate(“signed message”)
a specific purpose.
Likewise, if a message has no purpose it won’t be returned when verifying with
@verifier.verify(token) # => raises ActiveSupport::MessageVerifier::InvalidSignature
@verifier.verify(token, purpose: :shipping) # => raises ActiveSupport::MessageVerifier::InvalidSignature
@verifier.verify(token, purpose: :login) # => “signed message”
@verifier.verified(token) # => nil
@verifier.verified(token, purpose: :shipping) # => nil
@verifier.verified(token, purpose: :login) # => “signed message”
Then that same purpose must be passed when verifying to get the data back out:
token = @verifier.generate(“signed message”, purpose: :login)
You can reduce this risk by confining signed messages to a specific :purpose.
action.
Doing so could allow a malicious actor to re-use a signed message to perform an unauthorized
It’s not recommended to use the same verifier for different purposes in your application.
=== Confine messages to a specific purpose
end
self.current_user = User.find(id)
if time.future?
id, time = Rails.application.message_verifier(:remember_me).verify(cookies)
Later verify that message:<br><br>cookies = Rails.application.message_verifier(:remember_me).generate([@user.id, 2.weeks.from_now])
First, generate a signed message:
where the session store isn’t suitable or available.
This is useful for cases like remember-me tokens and auto-unsubscribe links
.
to manage unique instances of verifiers for each use case.
In a Rails application, you can use Rails.application.message_verifier
signed to prevent tampering.
MessageVerifier makes it easy to generate and verify messages which are
= Active Support Message Verifier

def create_message(value, **options) # :nodoc:

:nodoc:
def create_message(value, **options) # :nodoc:
  sign_encoded(encode(serialize_with_metadata(value, **options)))
end

def digest_length_in_hex

def digest_length_in_hex
  # In hexadecimal (AKA base16) it takes 4 bits to represent a character,
  # hence we multiply the digest's length (in bytes) by 8 to get it in
  # bits and divide by 4 to get its number of characters it hex. Well, 8
  # divided by 4 is 2.
  @digest_length_in_hex ||= OpenSSL::Digest.new(@digest).digest_length * 2
end

def digest_matches_data?(digest, data)

def digest_matches_data?(digest, data)
  data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
end

def extract_encoded(signed)

def extract_encoded(signed)
  if signed.nil? || !signed.valid_encoding?
    throw :invalid_message_format, "invalid message string"
  end
  if separator_index = separator_index_for(signed)
    encoded = signed[0, separator_index]
    digest = signed[separator_index + SEPARATOR_LENGTH, digest_length_in_hex]
  end
  unless digest_matches_data?(digest, encoded)
    throw :invalid_message_format, "mismatched digest"
  end
  encoded
end

def generate(value, **options)

(See #verified and #verify.)
specified when verifying the message; otherwise, verification will fail.
The purpose of the message. If specified, the same purpose must be
[+:purpose+]

verifier.verify(message) # => raises ActiveSupport::MessageVerifier::InvalidSignature
verifier.verified(message) # => nil
# 24 hours later...
verifier.verified(message) # => "hello"
message = verifier.generate("hello", expires_in: 24.hours)

elapsed, verification of the message will fail.
The duration for which the message is valid. After this duration has
[+:expires_in+]

verifier.verify(message) # => raises ActiveSupport::MessageVerifier::InvalidSignature
verifier.verified(message) # => nil
# 24 hours later...
verifier.verified(message) # => "hello"
message = verifier.generate("hello", expires_at: Time.now.tomorrow)

verification of the message will fail.
The datetime at which the message expires. After this datetime,
[+:expires_at+]

==== Options

verifier.generate("signed message") # => "BAhJIhNzaWduZWQgbWVzc2FnZQY6BkVU--f67d5f27c3ee0b8483cebf2103757455e947493b"
verifier = ActiveSupport::MessageVerifier.new("secret")

Returns Base64-encoded message joined with the generated signature.
The message is signed with the +MessageVerifier+'s secret.

Generates a signed message for the provided value.
def generate(value, **options)
  create_message(value, **options)
end

def generate_digest(data)

def generate_digest(data)
  OpenSSL::HMAC.hexdigest(@digest, @secret, data)
end

def initialize(secret, **options)

+config.active_support.use_message_serializer_for_metadata+.
If you don't pass a truthy value, the default is set using

was the default in \Rails 7.0 and below.
message first, then wraps it in an envelope which is also serialized. This
Whether to use the legacy metadata serializer, which serializes the
[+:force_legacy_metadata_serializer+]

and Filename Safe Alphabet" in RFC 4648), you can pass +true+.
generate URL-safe strings (in compliance with "Base 64 Encoding with URL
not URL-safe. In other words, they can contain "+" and "/". If you want to
By default, MessageVerifier generates RFC 4648 compliant strings which are
[+:url_safe+]

Otherwise, the default is +:marshal+.
When using \Rails, the default depends on +config.active_support.message_serializer+.

these require the +msgpack+ gem.
not supported by JSON, and may provide improved performance. However,
ActiveSupport::MessagePack, which can roundtrip some Ruby types that are
The +:message_pack+ and +:message_pack_allow_marshal+ serializers use

possible, choose a serializer that does not support +Marshal+.
attacks in cases where a message signing secret has been leaked. If
not. Beware that +Marshal+ is a potential vector for deserialization
serializers support deserializing using +Marshal+, but the others do
The +:marshal+, +:json_allow_marshal+, and +:message_pack_allow_marshal+

to migrate between serializers.
ActiveSupport::JSON, or ActiveSupport::MessagePack. This makes it easy
will serialize using +Marshal+, but can deserialize using +Marshal+,
multiple deserialization formats. For example, the +:marshal+ serializer
The preconfigured serializers include a fallback mechanism to support

+:json+, +:message_pack_allow_marshal+, +:message_pack+.
several preconfigured serializers: +:marshal+, +:json_allow_marshal+,
object that responds to +dump+ and +load+, or you can choose from
The serializer used to serialize message data. You can specify any
[+:serializer+]

+OpenSSL::Digest+ for alternatives.
Digest used for signing. The default is "SHA1". See
[+:digest+]

==== Options

Initialize a new MessageVerifier with a secret for the signature.
def initialize(secret, **options)
  raise ArgumentError, "Secret should not be nil." unless secret
  super(**options)
  @secret = secret
  @digest = options[:digest]&.to_s || "SHA1"
end

def inspect # :nodoc:

:nodoc:
def inspect # :nodoc:
  "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
end

def read_message(message, **options) # :nodoc:

:nodoc:
def read_message(message, **options) # :nodoc:
  deserialize_with_metadata(decode(extract_encoded(message)), **options)
end

def separator_at?(signed_message, index)

def separator_at?(signed_message, index)
  signed_message[index, SEPARATOR_LENGTH] == SEPARATOR
end

def separator_index_for(signed_message)

def separator_index_for(signed_message)
  index = signed_message.length - digest_length_in_hex - SEPARATOR_LENGTH
  index unless index.negative? || !separator_at?(signed_message, index)
end

def sign_encoded(encoded)

def sign_encoded(encoded)
  digest = generate_digest(encoded)
  encoded << SEPARATOR << digest
end

def valid_message?(message)

verifier.valid_message?(tampered_message) # => false
tampered_message = signed_message.chop # editing the message invalidates the signature

verifier.valid_message?(signed_message) # => true
signed_message = verifier.generate("signed message")
verifier = ActiveSupport::MessageVerifier.new("secret")

with the +MessageVerifier+'s secret.
Checks if a signed message could have been generated by signing an object
def valid_message?(message)
  !!catch_and_ignore(:invalid_message_format) { extract_encoded(message) }
end

def verified(message, **options)


verifier.verified(message, purpose: "greeting") # => nil
verifier.verified(message) # => "bye"
message = verifier.generate("bye")

verifier.verified(message) # => nil
verifier.verified(message, purpose: "chatting") # => nil
verifier.verified(message, purpose: "greeting") # => "hello"
message = verifier.generate("hello", purpose: "greeting")

match, +verified+ will return +nil+.
The purpose that the message was generated with. If the purpose does not
[+:purpose+]

==== Options

verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"

Raises any error raised while decoding the signed message.

verifier.verified(invalid_message) # => nil
invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d"

Returns +nil+ if the message is not Base64-encoded.

other_verifier.verified(signed_message) # => nil
other_verifier = ActiveSupport::MessageVerifier.new("different_secret")

Returns +nil+ if the message was not signed with the same secret.

verifier.verified(signed_message) # => "signed message"
signed_message = verifier.generate("signed message")

verifier = ActiveSupport::MessageVerifier.new("secret")

Decodes the signed message using the +MessageVerifier+'s secret.
def verified(message, **options)
  catch_and_ignore :invalid_message_format do
    catch_and_raise :invalid_message_serialization do
      catch_and_ignore :invalid_message_content do
        read_message(message, **options)
      end
    end
  end
end

def verify(message, **options)


verifier.verify(message, purpose: "greeting") # => raises InvalidSignature
verifier.verify(message) # => "bye"
message = verifier.generate("bye")

verifier.verify(message) # => raises InvalidSignature
verifier.verify(message, purpose: "chatting") # => raises InvalidSignature
verifier.verify(message, purpose: "greeting") # => "hello"
message = verifier.generate("hello", purpose: "greeting")

match, +verify+ will raise ActiveSupport::MessageVerifier::InvalidSignature.
The purpose that the message was generated with. If the purpose does not
[+:purpose+]

==== Options

other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
other_verifier = ActiveSupport::MessageVerifier.new("different_secret")

secret or was not Base64-encoded.
Raises +InvalidSignature+ if the message was not signed with the same

verifier.verify(signed_message) # => "signed message"

signed_message = verifier.generate("signed message")
verifier = ActiveSupport::MessageVerifier.new("secret")

Decodes the signed message using the +MessageVerifier+'s secret.
def verify(message, **options)
  catch_and_raise :invalid_message_format, as: InvalidSignature do
    catch_and_raise :invalid_message_serialization do
      catch_and_raise :invalid_message_content, as: InvalidSignature do
        read_message(message, **options)
      end
    end
  end
end