Class: ActiveSupport::MessageVerifier
- Defined in:
- activesupport/lib/active_support/message_verifier.rb
Overview
MessageVerifier
makes it easy to generate and verify messages which are signed to prevent tampering.
This is useful for cases like remember-me tokens and auto-unsubscribe links where the session store isn’t suitable or available.
Remember Me:
[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
In the authentication filter:
id, time = @verifier.verify([:remember_me])
if Time.now < time
self.current_user = User.find(id)
end
By default it uses Marshal to serialize the message. If you want to use another serialization method, you can set the serializer in the options hash upon initialization:
@verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
MessageVerifier
creates HMAC signatures using SHA1 hash algorithm by default. If you want to use a different hash algorithm, you can change it by providing ‘:digest` key as an option while initializing the verifier:
@verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
Confining messages to a specific purpose
By default any message can be used throughout your app. But they can also be confined to a specific :purpose
.
token = @verifier.generate("this is the chair", purpose: :login)
Then that same purpose must be passed when verifying to get the data back out:
@verifier.verified(token, purpose: :login) # => "this is the chair"
@verifier.verified(token, purpose: :shipping) # => nil
@verifier.verified(token) # => nil
@verifier.verify(token, purpose: :login) # => "this is the chair"
@verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature
@verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature
Likewise, if a message has no purpose it won’t be returned when verifying with a specific purpose.
token = @verifier.generate("the conversation is lively")
@verifier.verified(token, purpose: :scare_tactics) # => nil
@verifier.verified(token) # => "the conversation is lively"
@verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature
@verifier.verify(token) # => "the conversation is lively"
Making messages expire
By default messages last forever and verifying one year from now will still return the original value. But messages can be set to expire at a given time with :expires_in
or :expires_at
.
@verifier.generate(parcel, expires_in: 1.month)
@verifier.generate(doowad, expires_at: Time.now.end_of_year)
Then the messages can be verified and returned upto the expire time. Thereafter, the verified
method returns nil
while verify
raises ActiveSupport::MessageVerifier::InvalidSignature
.
Defined Under Namespace
Classes: InvalidSignature
Instance Method Summary collapse
-
#generate(value, expires_at: nil, expires_in: nil, purpose: nil) ⇒ Object
Generates a signed message for the provided value.
-
#initialize(secret, options = {}) ⇒ MessageVerifier
constructor
A new instance of MessageVerifier.
-
#valid_message?(signed_message) ⇒ Boolean
Checks if a signed message could have been generated by signing an object with the
MessageVerifier
‘s secret. -
#verified(signed_message, purpose: nil) ⇒ Object
Decodes the signed message using the
MessageVerifier
‘s secret. -
#verify(signed_message, purpose: nil) ⇒ Object
Decodes the signed message using the
MessageVerifier
‘s secret.
Constructor Details
#initialize(secret, options = {}) ⇒ MessageVerifier
Returns a new instance of MessageVerifier.
79 80 81 82 83 84 |
# File 'activesupport/lib/active_support/message_verifier.rb', line 79 def initialize(secret, = {}) raise ArgumentError, "Secret should not be nil." unless secret @secret = secret @digest = [:digest] || "SHA1" @serializer = [:serializer] || Marshal end |
Instance Method Details
#generate(value, expires_at: nil, expires_in: nil, purpose: nil) ⇒ Object
Generates a signed message for the provided value.
The message is signed with the MessageVerifier
‘s secret. Without knowing the secret, the original value cannot be extracted from the message.
verifier = ActiveSupport::MessageVerifier.new 's3Krit'
verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
159 160 161 162 |
# File 'activesupport/lib/active_support/message_verifier.rb', line 159 def generate(value, expires_at: nil, expires_in: nil, purpose: nil) data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose)) "#{data}--#{generate_digest(data)}" end |
#valid_message?(signed_message) ⇒ Boolean
Checks if a signed message could have been generated by signing an object with the MessageVerifier
‘s secret.
verifier = ActiveSupport::MessageVerifier.new 's3Krit'
= verifier.generate 'a private message'
verifier.() # => true
= .chop # editing the message invalidates the signature
verifier.() # => false
95 96 97 98 99 100 |
# File 'activesupport/lib/active_support/message_verifier.rb', line 95 def () return if .nil? || !.valid_encoding? || .blank? data, digest = .split("--".freeze) data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) end |
#verified(signed_message, purpose: nil) ⇒ Object
Decodes the signed message using the MessageVerifier
‘s secret.
verifier = ActiveSupport::MessageVerifier.new 's3Krit'
= verifier.generate 'a private message'
verifier.verified() # => 'a private message'
Returns nil
if the message was not signed with the same secret.
other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
other_verifier.verified() # => nil
Returns nil
if the message is not Base64-encoded.
= "f--46a0120593880c733a53b6dad75b42ddc1c8996d"
verifier.verified() # => nil
Raises any error raised while decoding the signed message.
= "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
verifier.verified() # => TypeError: incompatible marshal file format
123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'activesupport/lib/active_support/message_verifier.rb', line 123 def verified(, purpose: nil) if () begin data = .split("--".freeze)[0] = Messages::Metadata.verify(decode(data), purpose) @serializer.load() if rescue ArgumentError => argument_error return if argument_error..include?("invalid base64") raise end end end |
#verify(signed_message, purpose: nil) ⇒ Object
Decodes the signed message using the MessageVerifier
‘s secret.
verifier = ActiveSupport::MessageVerifier.new 's3Krit'
= verifier.generate 'a private message'
verifier.verify() # => 'a private message'
Raises InvalidSignature
if the message was not signed with the same secret or was not Base64-encoded.
other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
other_verifier.verify() # => ActiveSupport::MessageVerifier::InvalidSignature
148 149 150 |
# File 'activesupport/lib/active_support/message_verifier.rb', line 148 def verify(, purpose: nil) verified(, purpose: purpose) || raise(InvalidSignature) end |