John
John

Reputation: 1323

How to encrypt and decrypt IDs in rails?

I have a requirement where user should be logged in, and should be landed in a specific page.

Basically user gets a link in his email, up on clicking of that link user will be logged in to system and landed in a specific page. Currently I am implementing this feature with the record ID(user id).

the url looks like this.

localhost:3000/details?user_id=412

def details
  @user = User.find(user_id)
  signin(@user)
end

because of the above url, anybody can randomly access the other user account by changing the ID. i want this id to be encrypted in url, and in controller action i want to decrypt it. i don't want this encryption through out the application i want to implement for only this action.

for example:

localhost:3000/details?user_id=SnrIcaMA8rikrnNX6RRKNw

def details
  @user = User.find(decrypt(user_id))
  signin(@user)
end

Upvotes: 2

Views: 2726

Answers (3)

Vlad
Vlad

Reputation: 901

What you need is pretty simple. You're making this sound more complicated than it should.

You can achieve this in two ways. I actually use both in my app.

  1. UUIDs. Instead of using /user/1 use /user/560a56b2-627b-4c28-a685-8d18674060cb . You can generate a UUID by using SecureRandom.uuid
  2. Check ownership. This is actual code I'm using in my app that uses Devise for User Authentication.

user_controller

before_action :check_ownership

def check_ownership
  redirect_to authenticated_root_path and return unless user == current_user || current_user.try(:admin)
end

Code is pretty self explanatory. It will redirect to root_path if current_user which has ID 1 is trying to access user/2. If user is admin, it will allow him to see user/2

Upvotes: 0

max
max

Reputation: 102001

Rails 5 introduced has_secure_token which like has_secure_password is intended to improve security by removing the need to reinvent the wheel. This uses a token exactly like in Sergio Tulentsev's excellent answer but the token is generated by SecureRandom.base58.

# Schema: User(token:string, auth_token:string)
class User < ActiveRecord::Base
  has_secure_token
  has_secure_token :auth_token
end

user = User.new
user.save
user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
user.auth_token # => "77TMHrHJFvFDwodq8w7Ev2m7"
user.regenerate_token # => true
user.regenerate_auth_token # => true

To authenticate a user based on the token you can use:

@user = User.find_by!(token: params[:token])

If you are using Devise or just Warden you can create a token authentication strategy.

module Devise
  module Strategies
    class Token < Base
      def valid? 
        params['token']
      end
      def authenticate!
        u = User.find_by(token: params[:token]) 
        if u 
           fail!("Invalid token")
        else
           success!(u) 
        end
      end
    end
  end
end

Upvotes: 4

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230336

No need to involve any encryption here.

The simplest solution would be to add a new field to users table, email_token (or something) and set it to any random value you want. SecureRandom.hex or SecureRandom.uuid are good examples.

Then you just use this token to generate links, and not the user id. Your links will look like this:

 example.com/details?token=d3fff15f2e1707719a2acbd4c1edd110

And in your controller you do something like

user = User.where(email_token: params[:token]).first
if user
  # show page
else
  # redirect to "not authorized" page
end

Good luck trying to guess another existing token.

One of advantages of this scheme over deriving some value from user id is that if some link/token gets posted to forums or something, you just change the compromised token, without affecting other users.

Upvotes: 7

Related Questions