Skip to content

Instantly share code, notes, and snippets.

@josevalim
Last active August 15, 2024 15:12
Show Gist options
  • Save josevalim/fb706b1e933ef01e4fb6 to your computer and use it in GitHub Desktop.
Save josevalim/fb706b1e933ef01e4fb6 to your computer and use it in GitHub Desktop.
# This snippet shows how TokenAuthenticatable works in Devise today.
# In case you want to maintain backwards compatibility, you can ditch
# devise's token mechanism in favor of this hand-rolled one. If not,
# it is recommended to migrate to the mechanism defined in the following
# snippet (2_safe_token_authenticatable.rb).
#
# In both snippets, we are assuming the User is the Devise model.
class User < ActiveRecord::Base
# You likely have this before callback set up for the token.
before_save :ensure_authentication_token
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.where(authentication_token: token).first
end
end
end
# With a token setup, all you need to do is override
# your application controller to also consider token
# lookups:
class ApplicationController < ActionController::Base
# This is our new function that comes before Devise's one
before_filter :authenticate_user_from_token!
# This is Devise's authentication
before_filter :authenticate_user!
private
# For this example, we are simply using token authentication
# via parameters. However, anyone could use Rails's token
# authentication features to get the token from a header.
def authenticate_user_from_token!
user_token = params[:user_token].presence
user = user_token && User.find_by_authentication_token(user_token.to_s)
if user
# Notice we are passing store false, so the user is not
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a
# sign in token, you can simply remove store: false.
sign_in user, store: false
end
end
end
# We could make the authentication mechanism above a bit more safe
# by requiring a token **AND** an e-mail for token authentication.
# The code in the model looks the same, we just need to slightly
# change the controller:
class ApplicationController < ActionController::Base
# This is our new function that comes before Devise's one
before_filter :authenticate_user_from_token!
# This is Devise's authentication
before_filter :authenticate_user!
private
def authenticate_user_from_token!
user_email = params[:user_email].presence
user = user_email && User.find_by_email(user_email)
# Notice how we use Devise.secure_compare to compare the token
# in the database with the token given in the params, mitigating
# timing attacks.
if user && Devise.secure_compare(user.authentication_token, params[:user_token])
sign_in user, store: false
end
end
end
@remy727
Copy link

remy727 commented Jan 3, 2022

We can refactor this line:
break token unless User.where(authentication_token: token).first
to
break token unless User.exists?(authentication_token: token)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment