Starting with version 6.1, ActiveRecord
will support looking for records using signed IDs. This feature can be handy when doing things like resetting passwords, sending invitations, or doing email verification.
Signed IDs have three characteristics:
- They are tamper-proof
- They can be set to expire
- They can be scoped to a particular purpose
How is this different from GlobalId?
The above seems oddly similar to Signed Global IDs, so you may be wondering, why was this feature introduced?
The main difference is that ActiveRecord
signed IDs can only be used when referring to a single concrete class, conversely GlobalId
can be used when the passed ID might respond to any number of classes.
How can I use signed IDs?
When Rails 6.1 is released all ActiveRecord
instances will respond to:
-
signed_id
to generate the token -
find_signed
to find a record using the token -
find_signed!
same as the above but instead of returningnil
when the record is not found an error will be raised
Here's a simple example:
signed_user_id = User.first.signed_id
User.find_signed(signed_user_id)
=> User.first
If you want to set a purpose and expiration to the token you can pass them as arguments to signed_id
, like this:
signed_user_id = User.first.signed_id(purpose: :email_confirmation, expires_in: 30.minutes.from_now)
User.find_signed(signed_user_id)
=> nil
User.find_signed(signed_user_id, purpose: :other)
=> nil
User.find_signed(signed_user_id, purpose: : email_confirmation)
=> User.first
If you don't want the query to silently fail, you can use the bang-method alternative:
User.find_signed!("invalid_token")
=> ActiveSupport::MessageVerifier::InvalidSignature
Rails will automatically generate a signed_id_verifier_secret
to be used when encoding and decoding the tokens. You can also provide your own in an initializer.
# config/initializers/active_record.rb
ActiveRecord::Base.signed_id_verifier_secret = "custom_verfifier_secret"
If you are interested in learning how this was implemented, take a look at the pull request where this feature was introduced.
Top comments (2)
For the
expiration
parameter you should not be using.from_now
(Rails 7). Instead, you should be calling it this way:expiration: 1.day
orexpiration: 10.minutes
Can they be used as an alternative to JWT for user authentication?