Save 37% off PRO during our Black Friday Sale! »

Symfony Security Demystified

Symfony Security Demystified

3dd28ad260d202a12c2e93fc28fad5d7?s=128

Marco Petersen

July 31, 2019
Tweet

Transcript

  1. Symfony Security Demystified

  2. Security is about user management

  3. Security is about user management protecting resources

  4. → Who are you? → Are you allowed (to do

    that)?
  5. → Who are you? ➡ Authentication → Are you allowed

    (to do that)? ➡ Authorization
  6. The Game Plan → authentication concepts → authorization concepts →

    how they're used in a framework project Example Project https://github.com/ocrampete16/symfony-security- demystified-talk
  7. btw, who dis? ! Marco Petersen " Software Developer @

    SensioLabs # @ocrampete16 $ marco.petersen@sensiolabs.de
  8. Authentication Who are you?

  9. User Providers load a user from a source $userProvider =

    new InMemoryUserProvider([ 'marco' => [ 'password' => 'p4$$w0rd', 'roles' => ['ROLE_USER'], ], ]); $user = $userProvider->loadUserByUsername('marco');
  10. User Providers load a user from a source $userProvider =

    new InMemoryUserProvider([ 'marco' => [ 'password' => 'p4$$w0rd', 'roles' => ['ROLE_USER'], ], ]); $user = $userProvider->loadUserByUsername('marco');
  11. None
  12. The User Checker ensures that the user meets certain requirements

    $userChecker = new UserChecker(); $userChecker->checkPreAuth($user); // authenticate user here $userChecker->checkPostAuth($user);
  13. The User Checker ensures that the user meets certain requirements

    $userChecker = new UserChecker(); $userChecker->checkPreAuth($user); // authenticate user here $userChecker->checkPostAuth($user);
  14. The User Checker ensures that the user meets certain requirements

    $userChecker = new UserChecker(); $userChecker->checkPreAuth($user); // authenticate user here $userChecker->checkPostAuth($user);
  15. The User Checker ensures that the user meets certain requirements

    $userChecker = new UserChecker(); $userChecker->checkPreAuth($user); // authenticate user here $userChecker->checkPostAuth($user);
  16. What does the default User Checker do? The User Checker

    does not do anything by default. Implement your own to check for things like: → disabled accounts → accounts past the trial period → expired credentials
  17. Password Encoders encode passwords $encoderFactory = new EncoderFactory([ User::class =>

    new PlaintextPasswordEncoder(), ]); $encoder = $encoderFactory->getEncoder($user); $hash = $encoder->encodePassword($plaintext, $salt);
  18. Password Encoders encode passwords $encoderFactory = new EncoderFactory([ User::class =>

    new PlaintextPasswordEncoder(), ]); $encoder = $encoderFactory->getEncoder($user); $hash = $encoder->encodePassword($plaintext, $salt);
  19. None
  20. Putting it all together

  21. const PROVIDER_KEY = 'default'; $unauthenticatedToken = new UsernamePasswordToken('marco', 'p4$$w0rd', PROVIDER_KEY);

    $authenticationManager = new AuthenticationProviderManager([ new DaoAuthenticationProvider( $userProvider, $userChecker, PROVIDER_KEY, $encoderFactory ), ]); $authenticatedToken = $authenticationManager->authenticate($unauthenticatedToken);
  22. const PROVIDER_KEY = 'default'; $unauthenticatedToken = new UsernamePasswordToken('marco', 'p4$$w0rd', PROVIDER_KEY);

    $authenticationManager = new AuthenticationProviderManager([ new DaoAuthenticationProvider( $userProvider, $userChecker, PROVIDER_KEY, $encoderFactory ), ]); $authenticatedToken = $authenticationManager->authenticate($unauthenticatedToken);
  23. const PROVIDER_KEY = 'default'; $unauthenticatedToken = new UsernamePasswordToken('marco', 'p4$$w0rd', PROVIDER_KEY);

    $authenticationManager = new AuthenticationProviderManager([ new DaoAuthenticationProvider( $userProvider, $userChecker, PROVIDER_KEY, $encoderFactory ), ]); $authenticatedToken = $authenticationManager->authenticate($unauthenticatedToken);
  24. const PROVIDER_KEY = 'default'; $unauthenticatedToken = new UsernamePasswordToken('marco', 'p4$$w0rd', PROVIDER_KEY);

    $authenticationManager = new AuthenticationProviderManager([ new DaoAuthenticationProvider( $userProvider, $userChecker, PROVIDER_KEY, $encoderFactory ), ]); $authenticatedToken = $authenticationManager->authenticate($unauthenticatedToken);
  25. const PROVIDER_KEY = 'default'; $unauthenticatedToken = new UsernamePasswordToken('marco', 'p4$$w0rd', PROVIDER_KEY);

    $authenticationManager = new AuthenticationProviderManager([ new DaoAuthenticationProvider( $userProvider, $userChecker, PROVIDER_KEY, $encoderFactory ), ]); $authenticatedToken = $authenticationManager->authenticate($unauthenticatedToken);
  26. Symfony uses the same class for authenticated and unauthenticated tokens

    // UserPasswordToken.php public function __construct( $user, $credentials, string $providerKey, array $roles = [] ) { // ... parent::setAuthenticated(\count($roles) > 0); }
  27. Symfony uses the same class for authenticated and unauthenticated tokens

    // UserPasswordToken.php public function __construct( $user, $credentials, string $providerKey, array $roles = [] ) { // ... parent::setAuthenticated(\count($roles) > 0); }
  28. $userProvider = new InMemoryUserProvider([ 'marco' => [ 'password' => 'p4$$w0rd',

    'roles' => ['ROLE_USER'], ], ]);
  29. $userProvider = new InMemoryUserProvider([ 'marco' => [ 'password' => 'p4$$w0rd',

    'roles' => ['ROLE_USER'], ], ]);
  30. $userProvider = new InMemoryUserProvider([ 'marco' => [ 'password' => 'p4$$w0rd',

    'roles' => [], ], ]); // ... // $token is unauthenticated! $token = $authenticationManager->authenticate($unauthenticatedToken);
  31. $userProvider = new InMemoryUserProvider([ 'marco' => [ 'password' => 'p4$$w0rd',

    'roles' => [], ], ]); // ... // $token is unauthenticated! $token = $authenticationManager->authenticate($unauthenticatedToken);
  32. $userProvider = new InMemoryUserProvider([ 'marco' => [ 'password' => 'p4$$w0rd',

    'roles' => [], ], ]); // ... // $token is unauthenticated! $token = $authenticationManager->authenticate($unauthenticatedToken);
  33. Authorization Do you have permission?

  34. Security voters vote whether an authenticated user can perform an

    action on a subject $voter = new BlogPostVoter(); // returns VoterInterface::ACCESS_GRANTED, // VoterInterface::ACCESS_ABSTAIN or VoterInterface::ACCESS_DENIED $vote = $voter->vote($token, $blogPost, ['edit']);
  35. Security voters vote whether an authenticated user can perform an

    action on a subject $voter = new BlogPostVoter(); // returns VoterInterface::ACCESS_GRANTED, // VoterInterface::ACCESS_ABSTAIN or VoterInterface::ACCESS_DENIED $vote = $voter->vote($token, $blogPost, ['edit']);
  36. The framework also uses Security Voters to check for roles

    and authentication states $roleVoter = new RoleVoter('ROLE_'); $authenticatedVoter = new AuthenticatedVoter( $authenticationTrustResolver ); // Does the user have the role `ROLE_USER`? $roleVoter->vote($token, null, ['ROLE_USER']); // Is the user logged in? $authenticatedVoter->vote($token, null, ['IS_AUTHENTICATED_FULLY']);
  37. The framework also uses Security Voters to check for roles

    and authentication states $roleVoter = new RoleVoter('ROLE_'); $authenticatedVoter = new AuthenticatedVoter( $authenticationTrustResolver ); // Does the user have the role `ROLE_USER`? $roleVoter->vote($token, null, ['ROLE_USER']); // Is the user logged in? $authenticatedVoter->vote($token, null, ['IS_AUTHENTICATED_FULLY']);
  38. The framework also uses Security Voters to check for roles

    and authentication states $roleVoter = new RoleVoter('ROLE_'); $authenticatedVoter = new AuthenticatedVoter( $authenticationTrustResolver ); // Does the user have the role `ROLE_USER`? $roleVoter->vote($token, null, ['ROLE_USER']); // Is the user logged in? $authenticatedVoter->vote($token, null, ['IS_AUTHENTICATED_FULLY']);
  39. Putting it all together $accessDecisionManager = new AccessDecisionManager([ new RoleVoter(),

    new BlogPostVoter(), ]); $hasRole = $accessDecisionManager->decide( $authenticatedToken, ['ROLE_USER'] ); $canEditBlogPost = $accessDecisionManager->decide( $authenticatedToken, ['edit'], $blogPost );
  40. Putting it all together $accessDecisionManager = new AccessDecisionManager([ new RoleVoter(),

    new BlogPostVoter(), ]); $hasRole = $accessDecisionManager->decide( $authenticatedToken, ['ROLE_USER'] ); $canEditBlogPost = $accessDecisionManager->decide( $authenticatedToken, ['edit'], $blogPost );
  41. Putting it all together $accessDecisionManager = new AccessDecisionManager([ new RoleVoter(),

    new BlogPostVoter(), ]); $hasRole = $accessDecisionManager->decide( $authenticatedToken, ['ROLE_USER'] ); $canEditBlogPost = $accessDecisionManager->decide( $authenticatedToken, ['edit'], $blogPost );
  42. Putting it all together $accessDecisionManager = new AccessDecisionManager([ new RoleVoter(),

    new BlogPostVoter(), ]); $hasRole = $accessDecisionManager->decide( $authenticatedToken, ['ROLE_USER'] ); $canEditBlogPost = $accessDecisionManager->decide( $authenticatedToken, ['edit'], $blogPost );
  43. When using the framework

  44. A simple security configuration security: providers: database_users: entity: { class:

    App\Entity\User, property: username } encoders: App\Entity\User: 'bcrypt' firewalls: main: anonymous: true form_login: true access_control: - { path: ^/profile, roles: ROLE_USER }
  45. framework providers: database_users: entity: { class: App\Entity\User, property: username }

    standalone $userProvider = new EntityUserProvider( $registry, App\Entity\User::class, 'username' );
  46. framework encoders: App\Entity\User: 'bcrypt' standalone $encoderFactory = new EncoderFactory([ App\Entity\User::class

    => new BCryptPasswordEncoder($cost), ]);
  47. framework firewalls: main: anonymous: true form_login: true standalone $authenticationManager =

    new AuthenticationProviderManager([ new AnonymousAuthenticationProvider($secret), new DaoAuthenticationProvider( $userProvider, $userChecker, PROVIDER_KEY, $encoderFactory ), ]);
  48. framework access_control: - { path: ^/profile, roles: ROLE_USER } standalone

    $accessDecisionManager = new AccessDecisionManager([ new RoleVoter(), ]); // if request matches ^/profile $granted = $accessDecisionManager->decide( $authenticatedToken, ['ROLE_USER'] );
  49. In Summary

  50. The security workflow is comprised of two phases. Authentication asks

    "Who are you?" Authorization asks "Are you allowed to do that?"
  51. User providers load users from a source. User checkers check

    loaded users. Password encoders encode passwords.
  52. Security voters vote to grant or deny access (or abstain).

    Symfony uses Security Voters to check roles and authentication states.
  53. $kernel->terminate($request, $response);

  54. None