Skip to content

Conversation

@braindeaf
Copy link

Adding BlahController.strategies config to allow only specific strategies to be run on a per-controller basis. The best example of this is we have at least 5 strategies for our User class, but when it comes to our API controllers we only want :jwt to run, ignoring database_authenticatable, yubi_key, etc.

This was my initial solution, before this pull request.

https://robl.me/posts/the-magical-devise-journey

RobL and others added 17 commits July 29, 2021 14:13
- It says that the option is added to devise_for, but it is actually added to the devise method in the model.
…odules

Fix comment in some modules [ci skip]
Update a couple other modules that still referred to `devise_for` to
point to `devise`, and make all of them more consistent. We can only
mention `devise`, that should be clear enough about it being options
for the model method.
…ence` (heartcombo#5357)

Remove `ActiveSupport::Dependencies.reference`

This was deleted from Rails: rails/rails@14d4edd

As far as I can tell, it was meant to add a performance boost at some point in the past but doesn't seem to do anything useful these days.
…5397)

Changes deprecated `ActiveSupport::Dependencies.constantize(model_name)` to `model_name.constantize`

Co-authored-by: Carlos Antonio da Silva <carlosantoniodasilva@gmail.com>
It appears setting the `rack.session` to a simple hash doesn't work
anymore as it now has a few additional methods Rails is relying on to
determine whether it's enabled or not:
rails/rails#42231

Failure:
    NoMethodError: undefined method `enabled?' for {}:Hash
    rails (f55cdafe4b82) actionpack/lib/action_dispatch/middleware/flash.rb:62:in `commit_flash'

Turns we we don't seem to need to set `rack.session` for the tests here.
    DEPRECATION WARNING: Using legacy connection handling is deprecated.
    Please set `legacy_connection_handling` to `false` in your application.
Add Rails 7 / main support
Use `AS::Dependencies` as before if we still can, otherwise use the new
direct `constantize` call for Rails 7+.

Leave a TODO to help remind us this can be removed once we drop support
to Rails versions prior to 7 in the future.
This commit adds support for latest Rails release.
Also note in the Changelog that Turbo is not fully supported yet.
@xijo
Copy link

xijo commented Dec 21, 2021

I'd like to bump this PR.

We have the same use case: API should only use token authentication, rest of the app tries all approaches. And I'd guess we're not be the only ones..

Anything speaking against integrating it into devise? Happy to help with the PR in case anything is still missing.

@braindeaf
Copy link
Author

braindeaf commented Dec 23, 2021

We are using this branch in production so it's been really invaluable.

# self.strategies = [:authenticatable, :database_authenticatable]
# self.strategies = { user: :authenticatable, admin: :database_authenticatable }
#
class_attribute :strategies
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on making this a controller instance method as opposed to a class attribute? Agreed that this would be useful -- I'm in the middle of implementing something similar for a multi-tenant app with a single scope but a few separate auth mechanisms -- but I think it could be helpful to have the request context available if possible.

I realize Warden encourages you to locate strategy eligibility in the strategy itself, but if we did want to pick explicitly in the controller based on the request or some runtime information, that would be possible with an instance method but not a class attribute.

Making this an instance method would also make this function a little closer to the way the auth_options work in Devise::SessionsController#create.: https://github.com/heartcombo/devise/blob/main/app/controllers/devise/sessions_controller.rb#L47

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems perfectly reasonable. I'll take a look.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed on your recommendation

mappings.each do |mapping|
opts[:scope] = mapping
warden.authenticate!(opts) if !devise_controller? || opts.delete(:force)
warden.authenticate!(*self.class.strategies_for(mapping), opts) if !devise_controller? || opts.delete(:force)
Copy link

@oehlschl oehlschl Jan 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll likely also want to respect any per-controller strategies here (and add self.class.strategies_for to this .authenticate call in Devise::SessionsController#create):

https://github.com/heartcombo/devise/blob/main/app/controllers/devise/sessions_controller.rb#L19

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that Devise::SessionsController probably doesn't include these controller helpers, but as part of this work, it may be worth considering how to allow custom strategies in that controller. (It would be a mildly more complete solution, even if it's just adding an empty auth_strategies or strategies method to that controller and respecting it in the authenticate call.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is now possible to override the SessionsController too :)

@oehlschl
Copy link

oehlschl commented Jan 7, 2022

Just for the record, another solution I've seen encouraged for this problem is to set something in the Rails/request env in the controller and check it in the Strategy. Ex:

class Api::Users::SessionsController
  prepend_before_action :skip_token_strategy!, only: :create
  
  private def skip_token_strategy!
    request.env['devise.skip_token_strategy'] = true
  end
end

class Devise::Strategies::Token
  def valid?
    !env['devise.skip_token_strategy']
  end
end

Not suggesting anything one way or the other; just anticipating what other responses to this proposal might be. (Ex: "This can already be done, and strategy eligibility / validity should be determined in the Strategy itself.")

@braindeaf
Copy link
Author

braindeaf commented Jan 8, 2022

Not sure how I feel about that solution. A Strategy should be able to understand what is valid or not sure. Say, if it's a JWT token request and maybe forcing the request to be JSON. That I could understand. But deciding on whether to force the request to be invalid for some other arbitrary reason seems unreasonable to inject a reason to cause the Strategy to fail, not to mention that it will run every Strategy which is unnecessary if you know you only want to run one of them.

Not sure what the hell has happened after my merge of upstream, now I have 23 file changes :S

@braindeaf braindeaf marked this pull request as draft January 8, 2022 01:36
@braindeaf braindeaf marked this pull request as ready for review January 8, 2022 22:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

9 participants