Rails devise, find current_user by session_store key - ruby-on-rails

We have a production system (main app) running on Rails with devise gem configured (using Redis session store)(session is enabled for all subdomain)
I need to build a custom authentication service for one of our subdomain based microservice. For each request, this microservice will be calling our main app with session id (the cookie value of rails session store key) (which is configured in config/initializers/session_store.rb).
So the question is, can I find the user just by session id? (Using Devise or Warden)

You can do this with a method Devise's Authenticatable module adds to your resource called serialize_from_session. You can pass in an expanded Warden session variable, which includes both the key and salt (serialize_from_session accepts both of those as parameters). For example, if you are using Devise on a User model:
user = User.serialize_from_array(*session['warden.user.user.key'])
The session['warden.user.user.key'] variable is an array of the record ID (as an array) and a salt. It would look something like [[1], "$2f$23$2DIUF7Vr.J5sbfGwHTew"].

Related

Devise login with computed / manipulated username param

I'm trying to create a login using devise with a username that I've computed. As an example, let's assume we want to namespace our usernames - and I want to receive a username from the login form, and use namespaced_username to do the actual authentication.
So in my User::SessionsController#create, I might have:
def create
params[:user][:namespaced_username] = "namespace/#{params[:user][:mobile_number]}"
super
end
Even though devise is listening for namespaced_username (configured with authentication_keys in either initializers or the model itself), and with a user setup as namespace/username, I still get told Invalid Namespaced username or password is not valid
How can I get devise (the warden strategies, specifically) to read the new param?
It turns out this is a Rails 5 problem. Basically, Warden (which is used by devise) is handed the request object, probably in middleware, and then the controller is handed a copy of the request's params hash.
The easy way to fix it is, within User::SessionsController#create, we need to add a line:
def create
# Here we change params - but this won't be seen by the warden strategy
params[:user][:namespaced_username] = = "namespace/#{params[:user][:mobile_number]}"
# Inject our changes into the copy in request
request.params[:user].merge!(params[:user])
# now our changes will be seen by warden - continue with devise:
super
end

Devise Database Migration - Ruby on Rails

I have a Ruby on Rails (4.2.1) Application that use Devise (3.5.1) for authenticate users, we are rebuilding this application in Rails (5.0.0) and we are using Devise (4.2.0). The problem happen when I copy the users table from the old application to the new application, then in the new application I can not login using the old data. In the devise.rb initialiser I'm using the same secret_key in both applications so not sure why I can not login into the new app using the old data, any ideas?
To start with, use rails console to ensure the issue is connected with passwords, and not the app - i.e. that valid_password? call of your user model will fail with proper password.
Devise uses this method by default to generate password hashes:
def self.digest(klass, password)
if klass.pepper.present?
password = "#{password}#{klass.pepper}"
end
::BCrypt::Password.create(password, cost: klass.stretches).to_s
end
It uses klass.pepper to add to your password if present. klass here would be your model (e.g. user), which can be configured to use pepper:
Besides :stretches, you can define :pepper, :encryptor,
:confirm_within, :remember_for, :timeout_in, :unlock_in among other
options.
cost is complexity of salt generation for new password storing, so should only influence new hashes generation, and wouldn't affect validation of previously generated passwords.
Devise uses this method to compare input password with stored hashed value:
def self.compare(klass, hashed_password, password)
return false if hashed_password.blank?
bcrypt = ::BCrypt::Password.new(hashed_password)
if klass.pepper.present?
password = "#{password}#{klass.pepper}"
end
password = ::BCrypt::Engine.hash_secret(password, bcrypt.salt)
Devise.secure_compare(password, hashed_password)
end
Just debug it in your both app versions to see what input parameters in this method might be different, e.g. if you have different pepper defined for your model, bcrypt.salt is different in your two apps for the same hashed_value.
As to where salt is taken from existing stored hashed password, it's really simple. The stored string is simply split by $ symbol:
# call-seq:
# split_hash(raw_hash) -> version, cost, salt, hash
#
# Splits +h+ into version, cost, salt, and hash and returns them in that order.
def split_hash(h)
_, v, c, mash = h.split('$')
return v.to_str, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
end

rails devise hook into on_login

I'm looking to hook into devise after login / after session create. How would I go about doing this?
Basically I want to set a users location every time they login, and to do that I need an after login hook of sorts.
Devise is built on Warden, so you can use Warden's after_authentication hook.
Put this in an initializer:
Warden::Manager.after_authentication do |user,auth,opts|
# do something with user
end
The remote IP address and other request info is stored in auth.request (i.e. auth.request.remote_ip).
See https://github.com/hassox/warden/wiki/callbacks
Devise updates the value of the user.current_sign_in_at timestamp on successful login. So, you could simply add a before_save filter to your User model. In that filter, check to see if the value of this field has changed, and if it has, set the users location.
BTW - I'm not sure what you mean by "location" - if you mean IP address, Devise already stores that for you.
Here's a page from the devise wiki: How To: Redirect to a specific page on successful sign in.
In summary, the recommendation is to add the following method to the application controller:
app/controllers/application_controller.rb
def after_sign_in_path_for(resource)
custom_location_for(resource) || welcome_path
end
In the above code, resource means the object (user, account, etc) that you've implemented devise authentication for. (The object that has the devise_for in your routes.)

Using devise "rememberable" without cookies

I have a working Rails site that uses devise to manage users. For session management, I am using devise's rememberable strategy, which stores and retrieves encrypted authentication information from a user's cookie.
I'm implementing a multi-photo upload widget that uses flash. Flash does not support sending cookies along with requests. This is a problem with multiple multi-upload flash+javascript libraries, so fixing this shortcoming is probably not feasible.
So my question is: can I successfully authenticate to devise/rememberable without using cookies? And if so, how?
More details
Devise/rememberable depends on the value of remember_token within the cookie. If I could fool Rails into thinking that the value was supplied as a cookie (e.g. request.cookies['remember_token'] = '...'), my problem would be solved. Devise/rememberable would find the correct value there, unpack it, and successfully authenticate. However, the request.cookies hash is apparently read-only. Writing to the hash is silently ignored. Example (debug console from an incoming POST request):
>> request.cookies['remember_token'] = 'a string'
=> "a string"
>> request.cookies['remember_token']
=> nil
>> request.cookies
=> {}
I'm using (or trying to use) the FancyUpload v3 widget.
How about overriding Devise slightly?
Based on Devise 1.2.rc something like this should work:
module Devise
module Strategies
class Rememberable
def remember_cookie
# your code to get the hashed value from the request
end
end
end
end
Alternatively, you could add a new (subclassed) strategy:
module Devise
module Strategies
class RememberableParameter < Rememberable
def remember_cookie
# your code to get the hashed value from the request
end
end
end
end
Warden::Strategies.add(:rememberable_parameter, Devise::Strategies::Rememberable)
Or, look into Token Authenticatable:
Token Authenticatable: signs in a user based on an authentication token (also known as
"single access token"). The token can be given both through query string or
HTTP Basic Authentication
There's more about it here:
https://github.com/plataformatec/devise/blob/master/lib/devise/models/token_authenticatable.rb
Good luck!

Ruby on Rails: how can I store additional data in the session for authlogic

I'm using Authlogic to authenticate users.
I understand how to create and use sessions, but want to store an additional id variable in the current_user session created by authlogic.
Can I just do something like this:
session[:authlogic_sess_name] = #extra_id.id
However, I'm not sure what the authlogic session is named though or how to access it.
Thanks!
Why would you not just store the value in the session?
session[:extra] = #extra_id.id
The Authlogic current_user is simply a value in the current session, managed by the Rails stack itself.
I agree with Toby, if you don't need this additional attribute to be on AuthLogic's UserSession object, then it may be simpler to store the value in the session hash itself.
But in my specific case, and perhaps in yours too, I have information that I want on the UserSession record specifically rather than the session hash, because I want to access it in a callback in the UserSession model, where "session" is unavailable.
Here is an older blog post describing how to store an additional attribute on UserSession:
http://railsblog.kieser.net/2010/03/authlogic-custom-logins-and-persisting.html
EDIT: I didn't have much luck with this approach myself. To do the things that required information from the session hash, I put that logic in the controller instead of the model.
AuthLogic actually has certain callbacks that execute controller methods. For instance, AuthLogic will call last_request_update_allowed? if your controller responds to that, right before setting last_request_at.

Resources