Last active
August 15, 2024 15:12
-
-
Save josevalim/fb706b1e933ef01e4fb6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
We can refactor this line:
break token unless User.where(authentication_token: token).first
to
break token unless User.exists?(authentication_token: token)