Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Symfony Security rework tracking issue #30914

Open
curry684 opened this Issue Apr 6, 2019 · 8 comments

Comments

Projects
None yet
6 participants
@curry684
Copy link
Contributor

commented Apr 6, 2019

After discussions at EU FOSSA Hackathon we have some ideas on how to rework the Security component. I'm writing this "tracker issue" to gather up the info and choices made thus far.

The biggest functional issues have been discussed frequently over the years (#10316 and others) that the authentication makes some shortcut assumptions no longer valid in 2019:

  • The object of security considerations is not always a user, it may be a device, a computer, an API key, or any number of other abstract things. In the security world the concept of "something that can participate in security questions" is called a Principal.
  • There should be a functional separation between the Principal performing an action and the Context he is in while doing so. Authorization decisions should be based on the presented Context. This solves many practical issues like impersonation and "act as company".
  • Principals may be authenticated by various means, and be oblivious themselves on the subject. Principals should therefore not expose functionality to get passwords or roles as they may not have any. Tokens handle authentication.

The Symfony Security component was originally based on the Java Spring Security framework, but has deviated over the years. Today Spring Security has both fixed some structural issues that Symfony has (the notion of principals versus users and no central assumption that passwords and roles exist) and has some features that are currently lacking or hacky at best (structural support for OpenID/OAuth, support for 2FA). Conclusion for now is that most of the current Security component, especially authorization, is mostly fine and does not need a lot of work, and it wouldn't be all that hard to fix the authentication parts, and more strongly decouple authentication and authorization, by taking some current inspiration from Spring again.

Once these changes are propagated a lot of other features related to authentication should be easier to implement. Wishlists have been assembled:

Authentication

  • User impersonation (including multiple impersonation without having to log out)
  • Social login (login in the app using Google, Facebook, GitHub, Twitter, etc.) and generic OAuth support
  • Login throttling (limit the number of failed login attempts over a period of time)
  • Simultaneous session limiting (e.g. each user can login only from one device at the same time)
  • Two-factor (or multi-factor) authentication (also see #28868)
  • “Sudo mode” (trigger a new authentication before sensitive operations, like GitHub)
  • Native JWT implementation if some package is available (firebase/php-jwt ?)
  • CSRF on router level
  • User checkers (allow to make checks after user has logged in to block the log in if needed)
  • LDAP (authenticate against a LDAP server)
  • Support for third-party authentication services (e.g. Auth0: https://auth0.com/)
  • Support for SAML 2.0 (authentication and federation mechanism)
  • Support pre-authentication mechanisms (e.g. X.509 certificates, Request-Header authentication, etc.)
  • Have a centralized token invalidation mechanism (#30914 (comment))
  • Easy to add new authentication mechanisms (as Guard currently offers)
  • Easy customization of provided authentication mechanisms (especially form_login, which is the most complex and most used one): through events/hooks? Or implement those as Guards, we can easily decorate/override?
  • More events: at least having an event before a login is attempted, which could be used to implement custom rate-limiting mechanism
  • Constant timing user login for non-existent user (to protect leaking existence of a user) - see https://github.com/paragonie/airship/blob/8f04f071c414c3893cf66311839d20a343af1237/src/Engine/Security/Authentication.php#L161-L168 for technical details

Authorization

  • "Access control" to set permissions over web site sections using regexps
  • Native @Security annotation to set the permissions of controller classes and/or actions
  • Grant or deny permissions based on trust levels, such as whether 2FA is enabled or "sudo mode" is required
  • Having an opinionated way to set up object-level control mechanism (CRUD). Voters tend to be a little bit too general in my opinion, making necessary to set up per project conventions (ie, you have to check for “EDIT” attribute…), which can’t be enforced programmatically now.

Users

Dropping the notion of users at the lowest level must not mean DX suffers and developers need to write a lot of boilerplate again. In fact directly implementing PrincipalInterface should likely be considered an advanced subject, and we should provide a layer with abstract and stock implementation of common user-based scenarios:

  • Allow to represent application users with a User class (bonus: help you create it with make:user).
  • Allow to get a User object very easily from a Twig template.
  • Allow to easily differentiate anonymous users and logged in users.
  • “Remember Me” feature (access to the app without having to log in again; and allow to differentiate those users from both anonymous users and users who logged in for real)
  • Roles (to have different kinds of users: normal users, admins, etc.)
  • Allow to hash user passwords (Bcrypt, Argon2i, etc. but not old algorithms with custom salts)
  • Password rehashing (to upgrade from Bcrypt to Argon2i transparently)
  • "I forgot my password" (provide a default implementation for that or simplify a lot its implementation) (Note: currently under review in the MakerBundle)
  • Allow to log in users programmatically in an easy way (e.g. $security->login(‘username’))
  • Make it trivial to test protected resources (logging in a user in tests should be very easy)
  • Password validators (check if password is too simple, if it has been compromised before, etc.)
  • Have minimal requirements/assumptions about User class from Security component, so decoupling is easier (which is a pretty popular topic, actually). Only requiring username/password, likely?

Non-wishlist

  • As we are committed to using voters for authorization, and do not want to have/support 2 mechanisms serving similar or mostly identical purposes we will not implement ACLs again

/cc @wouterj @derrabus @linaori @sstok

@quentinus95

This comment has been minimized.

Copy link
Contributor

commented Apr 8, 2019

Following your comment on #28868, I wanted to add what I wrote there about authentication.

The current implementation considers authentication as a binary state (authenticated / not authenticated), and we use additional roles and rules (IS_AUTHENTICATED_FULLY) to determine the amount of trust we give to the authenticated principal.

The main issue is that we use authorization to describe something purely related to authentication. Something interesting could be to change this idea of binary status and replace it with a level of trust (e.g. an authentication through a form_login gives you 100 points). Having a 2FA form / using the remember me feature / having multiple factors could have an impact on this level of trust (e.g. using login_form + 2fa gives you a total of 150 points, using remember me gives you a total of 80 points, ...).

We could use this mecanism to ease authentication controls by defining, for instance, a minimal level of trust either using firewalls or path based security rules. This would give a lot of flexibility to craft advanced authentication mecanisms.

@curry684

This comment has been minimized.

Copy link
Contributor Author

commented Apr 8, 2019

@quentinus95 interesting thoughts. You should however remember that the process of authentication itself is handled by Tokens, so the process of how an authentication chain ends up at a certain level of trust should be handled there. This is even already being improved by the proposal at #30765.

In a nutshell right now we're pretty limited with the RememberMeToken granting IS_AUTHENTICATED_REMEMBERED, and just about every other one ends up with IS_AUTHENTICATED_FULLY. We'll need more granular control there to indeed implement some of the scenarios you describe and are also in some form in the to-do list of this RFC, specifically

  • Require 2FA to be enabled for the user for certain highly dangerous actions
  • Implement "sudo mode", requiring reauthentication to temporarily get into an "elevated" authentication
@curry684

This comment has been minimized.

Copy link
Contributor Author

commented Apr 8, 2019

Updated RFC with some minor things:

  • Added trust levels to authorization section
  • Renamed Identity to Context to comply with Spring terminology and to reduce confusion with .NET which implement Identity a bit differently)
  • Clarified object level permission description to explicitly mention CRUD, there should be some easily usable extension to Voters to implement CRUD functionality at object/entity levels
@vudaltsov

This comment has been minimized.

Copy link
Contributor

commented Apr 8, 2019

@curry684 , this is a great list, thank you!

I have some thoughts:

  • Manual authentication. Useful when authenticating user right after he has signed up. This is a common question which is usually solved by copying https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Security/LoginManager.php and a hackish compiler pass for remember me into the project.

  • A centralized token invalidation mechanism. It seems that tokens should not validate themselves — it's not flexible and extensible. Consider a scenario, when some security issue happens at the website, and you need to invalidate all logins after a fix. Currently there's no easy way to deactivate RememberMeTokens. Once in Slack I proposed to add a TokenValidatorInterface.

    interface TokenValidatorInterface
    {
        public function supports(TokenInterface $token): bool;
    
        public function isValid(TokenInterface $token/*, $someOtherArgs */): bool;
    }

    By the way, the code from AbstractToken::hasUserChanged() could be implemented under such an interface easily.

@teohhanhui

This comment has been minimized.

Copy link
Contributor

commented Apr 11, 2019

Relevant comment on password rehashing: #31019 (comment)

@curry684

This comment has been minimized.

Copy link
Contributor Author

commented Apr 12, 2019

@vudaltsov first one is already on the list as manual authentication, second one added, thanks!

@teohhanhui given that in that issue "the framework" has made a choice on the subject I'm not seeing an RFC item there. It's of course a valid discussion but not something to have here right now I think.

@curry684

This comment has been minimized.

Copy link
Contributor Author

commented Apr 12, 2019

Hmm interesting, thanks 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.