Rails tried to solve a lot of problems out of the box, but every project I’ve ever seen has authentication. There is, of course, devise but having user management, authentication, OTP and so on baked in would, in my opinion, be more beneficial than ActionStorage.
That’s an excellent idea. Seeing as Devise was recently cut free from its parent organization, maybe it could be scooped up and put into the mix. I’ve been using Devise nearly as long as I’ve been using Rails, certainly as long as I’ve been developing Rails apps for customers. I agree that Rails ought to offer a bundled authentication system “out of the box”.
I’m not sure about this. It’d be useful to “standardize” the authentication system among rails apps and have a quick way for adding them. But I feel that there are so many different kind of authentications (email/password, magic links, 2fa, oauth, and so on). I think that having a separate gem is useful to not constrain the release time or extensibility to the rails release schedule, but having an “open” release schedule based on the auth improvements/extensions. Each project also would have way more constraints based on their case by case (like not allowing self signups)
Devise is good for the omnibus, but I’d like to make more of the core ingredients directly accessible. Like we did with has_secure_password. We were thinking of doing this for 2FA and WebAuthn, by extracting the elemental pieces from HEY.
Not a big fan of doing anything that has any user-facing elements to it, though. So controllers and views is less appealing than model extractions. When you’re trying to extract elements that aren’t styled or given a specific flow.
What other elements from Devise might we think fit this criteria?
Also, I don’t think there’s a zero sum here. A good idea doesn’t have to justify itself with what it would replace, like “be more beneficial than ActionStorage”. Good ideas are good ideas, regardless of their relative merits to other good ideas!
And Rails has no built-in limit on the number of good ideas we’ll adopt.
Hey David, I relay like ActiveStorage and I should have stated that in the first post. Sorry for that.
Back to the idea: I really like what you are proposing. Rails could provide common (and, preferably, best in class :)) functionality like:
- user/password logic oit of the box (so, a Model, validations, hooks for lifecycle like “afer_register” for potential confirmation)
- 2fa, YubiKey and so on. Having this baked in would, alone, be a great benefit for the whole internet.
- session lifecycle management. I’m trying to make Devise work with a custom class for ActiveRecordSessionStore and it’s far from a pleasant experience and having a single cookie for every devise does not seems secure.
- authentication & maybe even authorization callbacks? Not sure about the latter, as it would need to be extremely configurable.
Also, some common ways we secure APIs like tokens or OAuth.
This could expose configuration parameters that gems and apps could use to provide the user-facing aspects or expand upon the provided mechanism, like a gem that would add Sign in with Apple.
The biggest thing here would be interoperability. If Rails provided this, a confirmable gem would work wit any omniauth provider gem.
Edit: another thought.
A user is an abstract resource that owns or belongs to resources. This is the base. However, often a lot is put into this abstract user, but login method is a separate entity. User has many login methods.
What I see as a thing that could be part of rails would this abstract user, a few basic login methods that may or may not function as 2FA. if not, rails could provide a mechanism for a given session to be tested via 2FA (with few baked in).
Having something like a standardized
Current.user) and a fleshed out authorization/policy API (I believe
action_policy is not bad) is something I would enjoy seeing, too. So to know that within a given Rails app you could call
current_user.policy.covers?(:read, @resource) (very pseudocode and without much thought or experience about the different APIs available in e.g. cancan, actionpolicy etc) and have a standard way to test these, too.
Pet peeve I would like to register, since there’s energy around this.
Most of the existing Rails world prefers/assumes that
current_user is nil if no registered user is logged in.
I dislike this for the following reasons:
current_registered_user. Anonymous users filling out a shopping cart are users too, but the way Devise and most other existing solutions assume that
current_user== “a user in the database, represented by a login cookie” trains developers into not realizing this, leading to complicated-to-maintain and insecure hacks whenever working with users who don’t register and log in via that solution’s standard flow.
- Projects accumulate null-check chains like
current_user&.latest_post&.top_comments&.[0..3]in the view layer, making views harder to understand and modify.
So if Rails did an
ActiveAuthentication I would like to ask: could it consider pushing developers to recognize that “anonymouses are users too” and that nearly every application has multiple user types? And perhaps even codify use of the null object pattern to reduce long, fragile
(coming full circle: I am fair sure that the very very first conversation Avdi and I ever had, something like a decade ago, was me going on exactly this rant.)
Just wanted to say that I’d love to see support for 2FA/WebAuthn in rails core.
FWIW I really like Pundit for authorization, though I’ve admittedly not tried action_policy
I like the idea of an ActiveAuthentication to make Rails even more batteries-included.
That’s why I wanted to echo the suggestions from @msapka / @felix.wolfsteller / @connorshea of adding authorization too. It seems like a natural feature to include with authentication. I like how Pundit did it by just having simple plain old Ruby policy objects under
Pundit and active_policy seem pretty similar. I haven’t used active_policy but I like Pundit.
The one thing that seems not-ideal about adopting the active_policy codebase wholesale to me is the way it has
user as a default (non-overridable?) name for the actor who the policy is authorizing. IME, because there’s inevitably a UserPolicy and confusion about which
user is which, it’s useful to call the user whose actions the policy is checking something like
I think pundit and other authorization libraries are over engineered… I prefer simple solutions like that DHH Policies
It’s overridable. From the docs:
useras authorization context. If you don’t need it, you have to build your own base policy.
Could you update us on the idea of extracting some aspect of Hey? Seems we have quite a few people agreeing that this is a good idea.
Opinion from a somewhat newbie - if there is no default authentication for whatever reason, there should be a guide for implementing it, or at least links to popular gems.
Also it seems worrying that session is just a hash - having some kind of standard “Visitor” object with the ability to attach models and other stuff to it would feel much better. For example, instead of
@current_user = User.find(session[:user_id])
before every action and some code on login it could be just
visitor.user = User.find(id)
on login, and rails could remember it for a session. Current session implementation gives me strong php flashbacks.
Also in the Agile Web Development with Rails 6 by Sam Ruby and David Bryant Copeland | The Pragmatic Bookshelf book they have a section on authentication if you want to roll your own.
I was just talking to a coworker the other day, “I can’t believe rails doesn’t offer guidance on authentication or authorization!” I’m so happy to see this mentioned here.
I’d like to echo what @lazaronixon said regarding authorization - for some apps having the overhead of POROs (a la pundit) for all the authorization logic seems like overkill. Cancan also has its set of tradeoffs too.
I’d also like to call out that authentication and authorization are both useful concepts, but they are different ones and perhaps each merits its own discussion.
With regards to authentication specifically I think it could be nice to borrow/extract from devise the
before_action :authenticate_user! pattern that you can use in your controllers from the get-go. Perhaps it could offer a basic authentication strategy, but allow you to opt-in to another authentication provider via config or
with: :devise or something?
I love the
ActiveSupport::CurrentAttributes pattern that was introduced in Rails 5.2 and maybe this could help people discover that pattern a bit easier if it guided them in that direction by default?
I agree that Rails shouldn’t provide any view generation out of the box for login/password reset etc, but maybe offer controller helpers a la
logout_current_user that helped ensure security best practices were being followed when people were using the baked-in authentication framework?
I share the same thoughts with @Jose_Valim (devise creator)… I always had the ideia to create a generator that implement a auhentication scaffold using CurrentAttributes, AccountMiddleware, has_secure_password, has_secure_token, default_scope, signed cookies and support shared controllers(web/api) through cookies/generated_token with options like…
- Single Tenancy = User
- Multi Tenancy = Account - User
- Multi Account = Account - Identity - User