How do I get AuthLogic to skip Password validation? - ruby-on-rails

I think I'm just missing something obvious. I send a user a perishable token embedded in a link. They click on it, and they come back to the site. I want to log them in automatically --- authenticated by their perishable token, not the password. (I'm not building a banking app).
This seems like this should be simple, but all the examples I've found require a password. How do I skip this completely? When I try to get UserSession.create to work, it reports a validation error and will not create the user session. What is the way around this?
#user = User.find_by_perishable_token(params[:token])
if #user
if !current_user
# skip sign-in
UserSession.create!(#user.email)
# => error "You did not provide any details for authentication."
...
I have googled extensively but haven't found the answer.

Doesn't UserSession.create take a user object as it's first argument? If so, couldn't you just do:
UserSession.create(User.find_by_perishable_token(params[:token]))
#current_user_session = UserSession.find
Or is that where you're running into problems?

Related

How to fix Brakeman redirect issue with multiple rest endpoints

I'm currently working on a solution for doing redirects in RoR because I got an error within the brakeman report saying that I have to fix redirects in a proper way.
I understand what the message says and how to solve it within one controller action.
But now I got the following. During the instantiation of the new method I set the HTTP_REFERER header which can be used in the create action.
This is giving me a Brakeman warning which can be found on the following link
Suppose I got the following controller with multiple endpoints:
def new
#my_model_set = MyModel.new
#referer = request.env['HTTP_REFERER'] # We want to redirect to this referer after a create
end
def create
...
if #my_model_set.save
flash_message :success, t('notification.item_created', type: #my_model_set.model_name.human)
if params[:referer].present?
redirect_to params[:referer]
else
redirect_to admin_my_model_set_path
end
else
...
end
end
I already tried to fix this by using the redirect_back method from RoR but that's using the referer link of the create method which I don't want to use.
if #my_model_set.save
flash_message :success, t('notification.item_created', type: #my_model_set.model_name.human)
redirect_back(fallback_location: admin_my_model_set_path)
else
...
end
The main problem in your code is that params[:referer] can be set by your user (or an attacker forging a link for your user) to an arbitrary value by appending ?referer=https://malicious.site to the url. You will then redirect to that, which is an open redirect vulnerability.
You could also argue that the referer header is technically user input, and you will be redirecting to it, but I would say in most cases and modern browsers that would probably be an acceptable risk, because an attacker does not really have a way to exploit it (but it might depend on the exact circumstances).
One solution that immediately comes to mind for similar cases would be the session - but on the one hand this is a rest api if I understand correctly, so there is no session, and on the other hand, it would still not be secure against an attacker linking to your #new endpoint from a malicious domain.
I think you should validate the domain before you redirect to it. If there is a common pattern (like for example if all of these are subdomains of yourdomain.com), validate for that. Or you could have your users register their domains first before you redirect to it (see how OAuth2 works for example, you have to register your app domain first before the user can get redirected there with a token).
If your user might just come from anywhere to #new and you want to send them back wherever they came from - that I think is not a good requirement, you should probably not do that, or you should carefully assess the risk and consciously accept it if you want to for some reason. In most cases there is a more secure solution.

Find_for_database_authentication vs Find_by in Rails Devise app?

So, I'm trying to set up a React frontend and Rails backend with devise, and the Rails side is supposed to be an internal API. It's the first time I've ever done this, so I'm struggling with authentication. Specifically, in my SessionsController, I have this code:
def create
resource = User.find_for_database_authentication(email: params[:email])
return invalid_login_attempt unless resource
if resource.valid_password?(params[:password])
sign_in :user, resource
return render nothing: true
end
invalid_login_attempt
end
This always returns 401 Unauthorized. I check the result of calling valid_password? and it is always false.
However, if I replace find_for_database_authentication with find_by, the valid_password? works with no problems. Why is this? It's okay if for now the user can only enter his email and not his password, but this really confuses me. It also bugs me that this doesn't use any token checking (different issue).
On the side, I'm also wondering about whether or not CSRF tokens are okay for internal APIs (should I use a different token-auth?), and how I'm supposed to include a CSRF token with a login form if the user isn't logged in yet, but I guess those are questions for another post. Thanks for any help.

Newbie with Rails devise and view of the user

I'm looking into RoR some way to: login into the system with DEVISE, (it's working), but i'm needing something than keeps always the view of this logged user, and avoid than this user looks another views.
http://xx.xx.xx.xx:3000/user/1
And this user cannot look the content of:
http://xx.xx.xx.xx:3000/user/2.
Please, sorry if this is a silly question, but, i was looking 2 days and i don't know how i can name this feature.
Thanks!
There are gems available for this Authorization. I prefer can can which is one of the best Authorization gems available
Here is the gem=> https://github.com/ryanb/cancan
And here is the rails cast tutorial using it=> http://railscasts.com/episodes/192-authorization-with-cancan
EDIT: If you want to manually implement this then you just need to make a method with following logic
def check_authorization
# Assuming user ID is coming in params[:id]
if current_user.id == params[:id]
return
else
# render or redirect to some page with access denied message
end
end
And call this method just before any action in which you want to check for authorization.

Restful Authentication -- how to log in a user without password

I've got a cross-website integration to handle. Basically I'm passing a param into the rails application and if it evaluates correctly ... then I'd like to log a user in.
Can this be done without the users password?
something like simply evaluating the password as true?
This is called "token authentication" and is supported by Devise, or can be relatively easily ginned up on your own. You want to generate a non-guessable secret token (your param), and then use that in lieu of a username. The devise wiki has links to a couple of examples:
https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
If you want a lighter-weight solution, you can also simply generate an auth token (using something like bcrypt) and then do something like:
#user = User.find_by_auth_token(params[:auth_token])
if #user is nil, then return a 403.

OmniAuth and Devise, how to set optional passwords

I am using OmniAuth and Devise to authenticate users. I would like users that have signed up using OmniAuth providers to be able to set an optional password (needed for API authentication) but I'm running into a wall.
If a user creates an account via OmniAuth and tries to set a password they get the following error:
BCrypt::Errors::InvalidHash in RegistrationsController#update
I believe this is because the password is blank. What's a good way around this? I've thought about generating a random password but the problem with that approach is the user needs to know the current password in order to edit settings.
Edit:
I looked at allowing the user to change settings without requiring a current password and that's what I would like to do only if the user didn't have a password initially.
An alternative is to add the following into your 'user' model class to bypass password verification if there is no password to verify, where provider is some field that is set when using external authentication.
def valid_password?(password)
!provider.nil? || super(password)
end
I assume you don't want the easy way out which would be to simply reset the password if they wanted to set it?
user.send_reset_password_instructions
This comes a bit late but it might help someone else, with Andrew's answer you can in create a password and store it in the database, but you can't login using your email and your new password, solved this by setting:
def valid_password
!provider.nil? && !encrypted_password.present? || super
end
Another alternative. You don't have to include a new field. Just catch the exception raised and return false. Here is the code.
def valid_password?(password)
begin
super(password)
rescue BCrypt::Errors::InvalidHash
return false
end
end
This should do the job.

Resources