Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Symfony Security Demystified

Symfony Security Demystified

Marco Petersen

July 31, 2019
Tweet

More Decks by Marco Petersen

Other Decks in Programming

Transcript

  1. 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
  2. User Providers load a user from a source $userProvider =

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

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

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

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

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

    $userChecker = new UserChecker(); $userChecker->checkPreAuth($user); // authenticate user here $userChecker->checkPostAuth($user);
  8. 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
  9. Password Encoders encode passwords $encoderFactory = new EncoderFactory([ User::class =>

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

    new PlaintextPasswordEncoder(), ]); $encoder = $encoderFactory->getEncoder($user); $hash = $encoder->encodePassword($plaintext, $salt);
  11. 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);
  12. 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);
  13. 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);
  14. 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);
  15. 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);
  16. 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); }
  17. 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); }
  18. $userProvider = new InMemoryUserProvider([ 'marco' => [ 'password' => 'p4$$w0rd',

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

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

    'roles' => [], ], ]); // ... // $token is unauthenticated! $token = $authenticationManager->authenticate($unauthenticatedToken);
  21. 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']);
  22. 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']);
  23. 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']);
  24. 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']);
  25. 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']);
  26. Putting it all together $accessDecisionManager = new AccessDecisionManager([ new RoleVoter(),

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

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

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

    new BlogPostVoter(), ]); $hasRole = $accessDecisionManager->decide( $authenticatedToken, ['ROLE_USER'] ); $canEditBlogPost = $accessDecisionManager->decide( $authenticatedToken, ['edit'], $blogPost );
  30. 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 }
  31. framework providers: database_users: entity: { class: App\Entity\User, property: username }

    standalone $userProvider = new EntityUserProvider( $registry, App\Entity\User::class, 'username' );
  32. framework firewalls: main: anonymous: true form_login: true standalone $authenticationManager =

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

    $accessDecisionManager = new AccessDecisionManager([ new RoleVoter(), ]); // if request matches ^/profile $granted = $accessDecisionManager->decide( $authenticatedToken, ['ROLE_USER'] );
  34. The security workflow is comprised of two phases. Authentication asks

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

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

    Symfony uses Security Voters to check roles and authentication states.