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. Symfony Security
    Demystified

    View full-size slide

  2. Security is about user management

    View full-size slide

  3. Security is about user management
    protecting resources

    View full-size slide

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

    View full-size slide

  5. → Who are you? ➡ Authentication
    → Are you allowed (to do that)? ➡ Authorization

    View full-size slide

  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

    View full-size slide

  7. btw, who dis?
    !
    Marco Petersen
    "
    Software Developer @ SensioLabs
    #
    @ocrampete16
    $
    [email protected]

    View full-size slide

  8. Authentication
    Who are you?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  15. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  18. Putting it all together

    View full-size slide

  19. 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);

    View full-size slide

  20. 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);

    View full-size slide

  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);

    View full-size slide

  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);

    View full-size slide

  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);

    View full-size slide

  24. 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);
    }

    View full-size slide

  25. 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);
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  31. Authorization
    Do you have permission?

    View full-size slide

  32. 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']);

    View full-size slide

  33. 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']);

    View full-size slide

  34. 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']);

    View full-size slide

  35. 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']);

    View full-size slide

  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']);

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  41. When using the framework

    View full-size slide

  42. 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 }

    View full-size slide

  43. framework
    providers:
    database_users:
    entity: { class: App\Entity\User, property: username }
    standalone
    $userProvider = new EntityUserProvider(
    $registry, App\Entity\User::class, 'username'
    );

    View full-size slide

  44. framework
    encoders:
    App\Entity\User: 'bcrypt'
    standalone
    $encoderFactory = new EncoderFactory([
    App\Entity\User::class => new BCryptPasswordEncoder($cost),
    ]);

    View full-size slide

  45. framework
    firewalls:
    main:
    anonymous: true
    form_login: true
    standalone
    $authenticationManager = new AuthenticationProviderManager([
    new AnonymousAuthenticationProvider($secret),
    new DaoAuthenticationProvider(
    $userProvider, $userChecker, PROVIDER_KEY, $encoderFactory
    ),
    ]);

    View full-size slide

  46. framework
    access_control:
    - { path: ^/profile, roles: ROLE_USER }
    standalone
    $accessDecisionManager = new AccessDecisionManager([
    new RoleVoter(),
    ]);
    // if request matches ^/profile
    $granted = $accessDecisionManager->decide(
    $authenticatedToken, ['ROLE_USER']
    );

    View full-size slide

  47. The security workflow is comprised of two phases.
    Authentication asks "Who are you?"
    Authorization asks "Are you allowed to do that?"

    View full-size slide

  48. User providers load users from a source.
    User checkers check loaded users.
    Password encoders encode passwords.

    View full-size slide

  49. Security voters vote to grant or deny access (or
    abstain).
    Symfony uses Security Voters to check roles and
    authentication states.

    View full-size slide

  50. $kernel->terminate($request, $response);

    View full-size slide