Doorkeeper - Reject access token request if user not an admin - ruby-on-rails

Using password-grant OAuth-2 flow, If a non-admin user requests an access token with scope: 'admin' I want to be able to reject that from happening - I'm using doorkeeper with devise.
Currently any old user can ask for any scope and by default it gets granted.
I don't see an obvious hook in doorkeeper to perform this sort of logic. Where/how should I configure this?

This is not currently possible with doorkeeper 3.x. You'd need to monkeypatch the OAuth::PreAuthorization class or fork the gem to add the required logic.
I had a similar need, so I made this into a config option where you can specify your own preauth class. The code is here. The doorkeeper maintainers indicated they wanted to think more about the feature.

Using Doorkeeper 4.x.x I am checking the presence of an 'admin' scope and if the current user is admin in the Doorkeeper's initializer:
resource_owner_from_credentials do
user = User.find_for_database_authentication(login: params[:login])
if user&.active_for_authentication? &&
user.valid_for_authentication? { user.valid_password? params[:password] }
admin_scope = params[:scope]&.split&.include?('admin')
user if admin_scope && user.admin? || !admin_scope
end
end
end

Related

How can we override from_basic method of doorkeeper

In doorkeeper gem of rails, access token get by the client by from_basic method of doorkeeper, i tried to customise this method, so that it is not easy for any outsider to get the access token.
I just add some more unique attribute with user name and email id, so that it become tough to get access token.
Here is the basic_method of doorkeeper:
def from_basic(request)
authorization = request.authorization
if authorization.present? && authorization =~ /^Basic (.*)/m
Base64.decode64(Regexp.last_match(1)).split(/:/, 2)
end
end
please provide me any suggestion how can i override this method
This is obviously security by obscurity, but it’s still easy with Module#prepend. Put this code anywhere in your initializers.
Doorkeeper::OAuth::Client.Credentials.
singleton_class.prepend(Module.new do
def from_basic(request)
original = super(request)
# my code that makes it better
end
end)

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.

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.)

Digest authentication in Devise

I'm using Rails 3 and Devise for authentication. I have a proper working devise for the website and basic authentication for API (json handler). How do I enable the digest authentication?
Their Wiki is telling me to add
def http_authenticate
authenticate_or_request_with_http_digest do |user_name, password|
user_name == "foo" && password == "bar"
end
warden.custom_failure! if performed?
end
Where do I add it to and how do I make user_name/password match?
That wiki entry sure assumes a lot.
My best guess is you need to add it to the appropriate controller (or the Application controller if you want it for everything).
And then add a :before_filter :http_authenticate!
You could also try tracking down the person who wrote that wiki page and asking them.
Note. This relies on Warden to perform your authentication - Devise only handles accounts.
One of the reasons this stuff isn't documented so well is most people use a sophisticated authentication management system (eg. OmniAuth), and something else for permissions/authorization eg. DeclarativeAuthorization or CanCan if you prefer something more light weight.
HTTPBasic (and I assume Digest) tends not to play nicely with these.

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