How to hide IDs from page URLs in Ruby on Rails
Ruby on Rails uses transparent URLs by default: x.com/users/4
signals that this is the 4th user on the site. But sometimes, you don't want to let someone know that they're your 4th user! This post will teach you how to hide these ids; in other words, to turn x.com/users/4
into x.com/users/x3g29z
.
To do this, we'll implement a gem called hashid-rails by Justin Cypret. It's an 80-line wrapper for the Hashids project, and it can do all the work from the model. This allows your database to use integers as IDs, but the ID will appear as a hash like x4g59d in the URL. Add it to your Gemfile:
gem "hashid-rails", "~> 1.0"
And then run bundle install
.
Create a file at config/initializers/hashid.rb, and add the following. Customize config.salt
to a word or phrase that you like - it'll scramble the ID to your project.
Hashid::Rails.configure do |config|
# The salt to use for generating hashid. Prepended with table name.
config.salt = "Mark is the best"
# The minimum length of generated hashids
config.min_hash_length = 6
# The alphabet to use for generating hashids
config.alphabet = "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"1234567890"
# Whether to override the `find` method
config.override_find = true
# Whether to sign hashids to prevent conflicts with regular IDs (see https://github.com/jcypret/hashid-rails/issues/30)
config.sign_hashids = true
end
Then, in whatever model you'd like a hashed URL, include Hashid::Rails
. This is what it would look like inside models/user.rb:
class User < ApplicationRecord
include Hashid::Rails
end
Now, start up your server, take a look at your user (or whatever type of record you're implementing this on), and it'll have a hashed URL!
The only problem is that it can still be accessed via its numerical URL. For instance, although you can access it at x.com/users/g7j3dV, you can still access it at x.com/users/2, too. Let's fix that so people can't look through your records sequentially.
I recommend turning off config.override_find
inside the Hashid initializer that we created earlier. This isn't necessary, but it helps us avoid shooting ourselves in the foot as we continue developing the application:
# Whether to override the `find` method
config.override_find = false
And inside your controller, use find_by_hashid
instead of find
:
# users_controller.rb
# ...
def set_user
@user = User.find_by_hashid(params[:id])
end
Now, people can't look at users via their ID – they need to access them via hash.
If you enjoyed this post, please make a t-shirt with my face on it.