For my lastest project I'm using https://github.com/hassox/rails_warden. It suits my needs very well except that I can't find a good way to implement remember_me. I know that it's notoriously difficult to get remember_me right from a security point of view so I'm hoping there's a project out there that will do the job. Anyone seen anything or get a good idea?
Ok here's how I solved it
# User model must have remember_token attribute
# in config.ru
use Rack::Cookies
run MyApp
# in lib/strategies.rb
Strategies.add(:cookie) do
def valid?
env['rack.cookies']['user.remember.token']
end
def authenticate!
if user = User.find_by_remember_token(cookies['user.remember.token'])
success! user
else
fail! "Could not log in"
end
end
end
Manager.after_authentication :scope => :user do |user, auth, opts|
auth.env['rack.cookies']['user.remember.token'] = user.generate_remember_token! # sets its remember_token attribute to some large random value and returns the value
end
Manager.before_logout :scoper => :user do |user, auth, opts|
user.update_attribute :remember_token, nil
end
Devise, which is an authentication solution on top of Warden, has a rememberable implementation.
Related
I'd like to build a web app based on ruby on rails. For authentication I am using the devise gem. Everything is good : I can create accounts, login, logout etc.
But here I have an issue. I'd like to be able to sign up without providing password but still have the ability to register with password for another account.
I've set the password lenght from 0 to 128 on config/initializers/devise.rb
config.password_length = 0..128
But what are the next steps to do what I want?
Thank you
Ok, I reply here as answer.
Thanks to Ammar Shah i figured out how to get users with password and users without password.
First of all create a file in lib/devise/strategies (create the folders) named database_authenticatable.rb with following code :
require 'devise/strategies/authenticatable'
module Devise
module Strategies
# Default strategy for signing in a user, based on their email and password in the database.
class DatabaseAuthenticatable < Authenticatable
def authenticate!
if password.blank?
authentication_hash[:encrypted_password] = ''
end
resource = mapping.to.find_for_database_authentication(authentication_hash)
hashed = false
if validate(resource){ hashed = true; resource.valid_password?(password) }
remember_me(resource)
resource.after_database_authentication
success!(resource)
end
mapping.to.new.password = password if !hashed && Devise.paranoid
fail(:not_found_in_database) unless resource
end
end
end
end
Warden::Strategies.add(:database_authenticatable, Devise::Strategies::DatabaseAuthenticatable)
Then in the devise_create_user.rb migration add :
t.string :remember_token
Finally in user.rb model :
before_create :remember_value
def valid_password?(password)
if password.blank?
true
else
super
end
end
def password_required?
new_record? ? false : super
end
def remember_value
self.remember_token ||= Devise.friendly_token
end
Thank you Ammar Shah for helping me !
Reset password length in config/initializers/devise.rb to the default value and use this answer to make password optional.
Also, here is a complete implementation of gradual engagement feature in devise wiki. It depends what exactly you want you achieve.
I am looking to set a secondary password by which I can authenticate a user for a login as from admin. The reason for this work around is the front end is a single page application.
Each user has been given a unique login_as string. now I need to configure Devise to compare the login_as if the password fails.
Any help is appreciated. I am of course open to an alternative solution if there is a better way.
Thanks.
This post from Duncan Robertson was very helpful in solving my issue. I essentially created an override strategy and called it in the devise.rb file. I had some concern regarding tampering with a large user base but it has proved successful. By adding a column to users named ":signin_as" and then setting it to a default unique string with a rake I then had what I needed to fallback on if the initial sign in failed.
the override strategy (config/initializers/auth_override.rb)
module Devise
module Strategies
class AuthOverride < Authenticatable
def custom_auth(user, signin_as)
if user[:signin_as] == signin_as
return true
else
return false
end
end
def authenticate!
user = User.find_by_email(email)
if user
if user.valid_password?(params[:password])
success!(user)
elsif custom_auth(user, params[:password])
success!(user)
else
fail
end
else
fail
end
end
end
end
end
including the strategy in devise (config/initializers/devise.rb)
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :auth_override
end
My setup: Rails 3.0.9, Ruby 1.9.2, Devise 1.3.4, Warden 1.0.4
I'm trying to figure out if it possible to authenticate a custom strategy and not have to create a devise user in the process upon successful authentication. In my config.warden block, the authentication works fine but if I don't create a Devise user, I won't be authenticated. My ideal scenario requires me to either successfully authenticate against a 3rd party provider and sign into my app (using Devise without a corresponding Devise user record) or if I fail to authenticate, then try Devise standard login path.
Here is the devise.rb code snippet I got working but I have to create a devise user for the authentication to work, this is something I wish to avoid
config.warden do |manager|
manager.strategies.add(:custom_strategy) do
def valid?
params[:user] && params[:user][:email] && params[:user][:password]
end
def authenticate!
...perform authentication against 3rd party provider...
if successful_authentication
u = User.find_or_initialize_by_email(params[:user][:email])
if u.new_record?
u.app = 'blah'
u.save
end
success!(u)
end
end
end
manager.default_strategies(:scope => :user).unshift :custom_strategy
end
I realized the question is old but I saw it a couple of time when I was searching for a solution to similar thing so I decided to post the answer in case anyone in the future stumbles upon similar issue. Hope this will help!
I recently had to do similar thing -> had users in my database that were authenticated with some devise/warden strategies but had created another app that has to have access to some of the endpoints to my application. Basically I wanted to do a HMAC authentication.
But I didn't want to involve any user objects in that process and here is what I had to do (provided that you already have you custom strategy that authenticates incoming request without using user object)
create a fake user model that is used so that devise wont blow op. You dont have to create any database table for that
mine looked similar to below:
class Worker # no need to create a table for him
extend ActiveModel::Callbacks
extend Devise::Models
include ActiveModel::Validations
include Concerns::ObjectlessAuthenticatable
define_model_callbacks :validation
attr_accessor :id
def persisted
false
end
def initialize(id)
#id = id
end
def self.serialize_from_session(id)
self.new(id: id)
end
def self.serialize_into_session(record)
[record.id]
end
def self.http_authenticatable
false
end
end
then in devise initializer (/initializers/devise.rb) I've added separate authentication strategy like below:
...
config.warden do |manager|
manager.scope_defaults :user, :strategies => [
...strategies i was using for users
]
manager.scope_defaults :worker, :strategies => [:worker_authentication], store: false, action: 'unautenticated_worker'
manager.failure_app = CustomFailingApp
end
...
then in routes.rb I had to create a mapping for devise to use like so
devise_for :worker # you can pass some custom options here
then wherever I needed to authenticate the worker, not the user I just had to call (in the controller) authenticate_worker!
I would expect that this is against the design of devise where all actions are done using restful routes for a resource. That said, the comments in Warden's success! method say:
# Parameters:
# user - The user object to login. This object can be anything you have setup to serialize in and out of the session
So could you not change the object u to some other object that represents the user, like a plain old Hash?
I am using Devise for rails 3 application. For page caching, I need to set cookies for log in/out info.
What's the simplest way to set cookies when log in/out occurrs with Devise? I read 'how to customize controller' part but it seems to be a lot of work.
Since Devise is based on Warden, another solution is to use Warden's callbacks, e.g in your devise.rb:
Warden::Manager.after_set_user do |user,auth,opts|
auth.cookies[:signed_in] = 1
end
Warden::Manager.before_logout do |user,auth,opts|
auth.cookies.delete :signed_in
end
It actually wouldn't be too hard to extend the devise SessionsController to add cookies on log in and log out, you could create a controller similar to this:
# app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
# POST /resource/sign_in
def create
cookies[:sign_in] = "Sign in info you want to store"
super
end
# GET /resource/sign_out
def destroy
cookies[:sign_out] = "Sign out info you want to store"
super
end
end
Then you would have to add the following to your routes.rb:
devise_for :users, :controllers => { :sessions => "sessions" }
That should get you most of the way there.
Adapted from #karl-rosaen answer this solution create a new initializer or add to the end of your devise.rb initializer.
This will add the cookie to remember the email if remember me options is set, if not it will delete the cookie
Warden::Manager.after_authentication do |user, auth, opts|
if user.remember_me
auth.cookies[:email] = {value: user.email, expires: 2.weeks.from_now}
else
auth.cookies.delete :email
end
end
I have a Rails app using Devise for authentication. Users belong to Dealers and I want to prevent users who belong to disabled dealers from being able to sign in.
Is there a straightforward way to extend Devise's authentication finder so that it will not include users from deleted dealers? Perhaps using a named scope on User?
Cheers
Tristan
Turns out all I needed to do was override my user model's find_for_authentication method:
class User < ActiveRecord::Base
...
# Intercept Devise to check if DealershipUser's Dealership is active
def self.find_for_authentication(conditions)
user = super
return nil if user.is_a?(DealershipUser) && user.dealership.deleted?
user
end
...
end
Find the user in the normal way by calling super.
I'm using STI so I check that the user is a DealershipUser and then check if the dealership is deleted (acts_as_paranoid).
Return the user.
This is a very specific solution for my scenario but you could override find_for_authentication however you like provided you return the user afterwards.
Searching Stackoverflow.com gave me this question/answer: Custom authentication strategy for devise
Basically, you have to implement a custom authentication strategy at Warden's level (that underlies Devise). For my project, I did the following:
In config/initializers/devise.rb:
Devise.setup do |config|
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :user_has_login_access
end
end
Warden::Strategies.add(:user_has_login_access) do
def valid?
# pass the commit parameter as 'login' or something like that, so that this strategy only activates when the user is trying to login
params[:commit] == 'login'
end
def authenticate!
u = User.find_by_email(params[:user][:email])
if u.can_login? # retrieves boolean value stored in the User model, set wherever
success! u
else
fail! "Account does not have login privilages."
end
end
end
You can read more about custom Warden strategies here: https://github.com/hassox/warden/wiki/Strategies
Hope that helps!