[SymfonyCon Paris] Dig in security

34ade09dd3d11004ca8ee4174fd3d6a2?s=47 Sarah KHALIL
December 09, 2015

[SymfonyCon Paris] Dig in security

Security in Symfony is often misunderstood. Let's have a look at what is really happening behind the opaque configuration of the Security Bundle. Then we will see how it is possible to implement any way to authenticate a user in Symfony without third party library.

34ade09dd3d11004ca8ee4174fd3d6a2?s=128

Sarah KHALIL

December 09, 2015
Tweet

Transcript

  1. DIG IN SECURITY WITH SYMFONY 2015

  2. WHO AM I? • Head of • Trainer & Developer

    • Enjoying sharer Sarah Khalil @saro0h
  3. WHAT’S THE PLAN?

  4. WHAT’S THE PLAN? Security: Authentication 1

  5. WHAT’S THE PLAN? Security: Authentication 1 Security: Authorization 2

  6. WHAT’S THE PLAN? Security: Authentication 1 What’s new in Symfony3

    3 Security: Authorization 2
  7. SECURITY IN SYMFONY

  8. Security Component

  9. Security Bundle

  10. None
  11. dump() var_

  12. dump()

  13. None
  14. Vote for him, he’s so swagg

  15. Vote for him, he’s so swagg

  16. Vote for him, he’s so swagg

  17. None
  18. None
  19. 4 KEY CONCEPTS User Provider Firewall Encoder

  20. WHAT DO WE HAVE TO WRITE?

  21. WHAT DO WE HAVE TO WRITE? security: encoders: Symfony\Component\Security\Core\User\User: bcrypt

    providers: in_memory: memory: users: sarah: password: $2a$12$LCY0M… roles: 'ROLE_USER' firewalls: admin: provider: in_memory pattern: /^admin form_login: login_path: /login check_path: /login_check
  22. AUTHENTICATION FLOW Firewall backend _username = ‘sarah.khalil’ _password = ‘mypwd’

    Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); Encoder Factory $factory->getEncoder($user); $encoder $encoder->isPasswordValid('mypwd'); Encoder true authenticated false | 401: Unauthorized /admin/myprofile or
  23. AUTHENTICATION FLOW Firewall backend _username = ‘sarah.khalil’ _password = ‘mypwd’

    Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); Encoder Factory $factory->getEncoder($user); $encoder $encoder->isPasswordValid('mypwd'); Encoder true authenticated false | 401: Unauthorized /admin/myprofile or
  24. 1. USER

  25. 1. USER

  26. WHAT IS A USER? The object stores: • credentials •

    associated roles
  27. MUST IMPLEMENT Symfony\Component\Security\Core\User\ UserInterface|AdvancedUserInterface

  28. 2. FIREWALL

  29. 2. FIREWALL

  30. Determines whether or not the user needs to be authenticated.

    AUTHENTICATION: FIREWALL
  31. Your application all routes begin with /admin all routes beginning

    with /shop all routes beginning with /blog all other routes…
  32. Your application all routes begin with /admin all routes beginning

    with /shop all routes beginning with /blog all other routes… The user needs to be authenticated to access this part of the app
  33. WHAT DO WE HAVE TO WRITE? security: firewalls: admin: provider:

    in_memory pattern: /^admin form_login: login_path: /login check_path: /login_check
  34. WHAT DO WE HAVE TO WRITE? security: firewalls: admin: provider:

    in_memory pattern: /^admin form_login: login_path: /login check_path: /login_check
  35. WHAT DO WE HAVE TO WRITE? security: firewalls: admin: provider:

    in_memory pattern: /^admin form_login: login_path: /login check_path: /login_check
  36. WHAT DO WE HAVE TO WRITE? security: firewalls: admin: provider:

    in_memory pattern: /^admin form_login: login_path: /login check_path: /login_check
  37. BUILT-IN WAYS OF AUTHENTICATING A USER

  38. http-basic http-digest BUILT-IN WAYS OF AUTHENTICATING A USER

  39. http-basic http-digest form-based BUILT-IN WAYS OF AUTHENTICATING A USER

  40. http-basic http-digest form-based x.509 certificate BUILT-IN WAYS OF AUTHENTICATING A

    USER
  41. http-basic http-digest form-based x.509 certificate BUILT-IN WAYS OF AUTHENTICATING A

    USER Your own authenticator
  42. AUTHENTICATION FLOW Firewall backend redirects to /admin/myprofile login form

  43. AUTHENTICATION FLOW Firewall backend _username = ‘sarah.khalil’ _password = ‘mypwd’

    authenticated 401: Unauthorized or /admin/login
  44. 3. PROVIDER

  45. AUTHENTICATION: PROVIDER Find and/or create a user.

  46. security: providers: in_memory: memory: users: sarah: password: $2a$12$LCY0M… roles: 'ROLE_USER'

  47. AUTHENTICATION: PROVIDER

  48. AUTHENTICATION: PROVIDER

  49. AUTHENTICATION: PROVIDER

  50. AUTHENTICATION: PROVIDER

  51. AUTHENTICATION: PROVIDER

  52. AUTHENTICATION: PROVIDER

  53. AUTHENTICATION: PROVIDER

  54. AUTHENTICATION: PROVIDER

  55. AUTHENTICATION FLOW Firewall backend _username = ‘sarah.khalil’ _password = ‘mypwd’

    Provider authenticated 401: Unauthorized /admin/myprofile or
  56. AUTHENTICATION FLOW Firewall backend _username = ‘sarah.khalil’ _password = ‘mypwd’

    Provider $provider->loadUserByUsername('sarah.khalil'); authenticated 401: Unauthorized /admin/myprofile or
  57. AUTHENTICATION FLOW Firewall backend _username = ‘sarah.khalil’ _password = ‘mypwd’

    Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); authenticated 401: Unauthorized /admin/myprofile or
  58. interface UserProviderInterface { public function loadUserByUsername($username); public function refreshUser(UserInterface $user);

    public function supportsClass($class); }
  59. 4. ENCODER

  60. AUTHENTICATION: ENCODER Hashes and compares a password.

  61. CONFIGURATION security: encoders: Symfony\Component\Security\Core\User\User: bcrypt SecurityBundle

  62. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    authenticated Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); /admin/myprofile
  63. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory authenticated Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); /admin/myprofile
  64. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory->getEncoder($user); authenticated Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); /admin/myprofile
  65. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory->getEncoder($user); $encoder authenticated Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); /admin/myprofile
  66. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory->getEncoder($user); $encoder Encoder authenticated Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); /admin/myprofile
  67. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory->getEncoder($user); $encoder Encoder $encoder->isPasswordValid('mypwd'); authenticated Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); /admin/myprofile
  68. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory->getEncoder($user); $encoder Encoder $encoder->isPasswordValid('mypwd'); authenticated true Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); /admin/myprofile
  69. AUTHENTICATION FLOW Firewall backend username = ‘sarah.khalil’ password = ‘mypwd’

    Encoder Factory $factory->getEncoder($user); $encoder Encoder $encoder->isPasswordValid('mypwd'); authenticated true Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’); /admin/myprofile false | 401: Unauthorized or
  70. interface PasswordEncoderInterface { /** * Encodes the raw password. */

    public function encodePassword($raw, $salt); /** * Checks a raw password against an encoded password. */ public function isPasswordValid($encoded, $raw, $salt); }
  71. ALL TOGETHER NOW!

  72. security: encoders: Symfony\Component\Security\Core\User\User: bcrypt providers: in_memory: memory: users: sarah: password:

    $2a$12$LCY0M… roles: 'ROLE_USER' firewalls: admin: provider: in_memory pattern: /^admin form_login: login_path: /login check_path: /login_check
  73. AUTHENTICATION FLOW backend

  74. AUTHENTICATION FLOW backend _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile

  75. AUTHENTICATION FLOW Firewall backend _username = ‘sarah.khalil’ _password = ‘mypwd’

    /admin/myprofile
  76. AUTHENTICATION FLOW Firewall backend Provider _username = ‘sarah.khalil’ _password =

    ‘mypwd’ /admin/myprofile
  77. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); _username = ‘sarah.khalil’ _password

    = ‘mypwd’ /admin/myprofile
  78. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’);

    _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile
  79. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’);

    Encoder Factory _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile
  80. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’);

    Encoder Factory $factory->getEncoder($user); _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile
  81. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’);

    Encoder Factory $factory->getEncoder($user); $encoder _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile
  82. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’);

    Encoder Factory $factory->getEncoder($user); $encoder Encoder _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile
  83. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’);

    Encoder Factory $factory->getEncoder($user); $encoder Encoder $encoder->isPasswordValid('mypwd'); _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile
  84. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’);

    Encoder Factory $factory->getEncoder($user); $encoder Encoder $encoder->isPasswordValid('mypwd'); true _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile
  85. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’);

    Encoder Factory $factory->getEncoder($user); $encoder Encoder $encoder->isPasswordValid('mypwd'); true authenticated _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile
  86. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’);

    Encoder Factory $factory->getEncoder($user); $encoder Encoder $encoder->isPasswordValid('mypwd'); true authenticated _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile or
  87. AUTHENTICATION FLOW Firewall backend Provider $provider->loadUserByUsername('sarah.khalil'); $user = new User(‘sarah.khalil’);

    Encoder Factory $factory->getEncoder($user); $encoder Encoder $encoder->isPasswordValid('mypwd'); true authenticated false | 401: Unauthorized _username = ‘sarah.khalil’ _password = ‘mypwd’ /admin/myprofile or
  88. AUTHORIZATION

  89. $decision = $this ->get(‘security.authorization_checker') ->isGranted('ROLE_ADMIN'); if (false === $decision) {

    throw $this ->createAccessDeniedException(‘Access denied.’); } What you have usually in a controller
  90. $decision = $this ->get(‘security.authorization_checker') ->isGranted('ROLE_ADMIN'); if (false === $decision) {

    throw $this ->createAccessDeniedException(‘Access denied.’); } What you have usually in a controller $this ->denyAccessUnlessGranted('ROLE_ADMIN', null, ‘Access denied!');
  91. Or in a template {% if is_granted('ROLE_ADMIN') %} <a href="...">Delete</a>

    {% endif %}
  92. HOW DOES IT WORK?

  93. None
  94. None
  95. Let’s take a look at the security.authorization_checker service.

  96. final public function isGranted($attributes, $object = null) { // Checks

    if there is someone authenticated (if there is a token) // Makes sure that the attributes in an array // returns true or false return $this->accessDecisionManager->decide($token, $attributes, $object); } Symfony\Component\Security\Core\Authorization\AccessDecisionManager Any string that represent a permission anything that can help to make a decision
  97. Symfony\Component\Security\Core\Authorization \AccessDecisionManager The object needs: • voters • a decision

    strategy • $allowIfAllAbstainDecisions • $allowIfEqualGrantedDeniedDecisions
  98. VOTERS Voters are added in a compiler pass (Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass )

    Must have the security.voter tag
  99. VOTERS Voters are sorted by the attribute priority (the highest

    is the last to be executed). If you don’t put any priority, => 0 (first to be executed)
  100. security: access_decision_manager: strategy: affirmative allow_if_all_abstain: false allow_if_equal_granted_denied: true in app/config/security.yml

  101. $strategy = $allowIfAllAbstainDecisions = $allowIfEqualGrantedDeniedDecisions = affirmative or consensus or

    unanimous true or false true or false
  102. $strategy = affirmative $allowIfAllAbstainDecisions = false $allowIfEqualGrantedDeniedDecisions = true Default

    values
  103. Symfony\Component\Security\Core\Authorization \AccessDecisionManager The object needs: • voters • a decision

    strategy • $allowIfAllAbstainDecisions • $allowIfEqualGrantedDeniedDecisions
  104. //Symfony\Component\Security\Core\Authorization\AccessDecisionManager public function decide(TokenInterface $token, array $attributes, $object = null)

    { return $this->{$this->strategy} ($token, $attributes, $object); } decideAffirmative decideConsensus decideUnanimous
  105. decideAffirmative Gives access if at least one voter says ACCESS_GRANTED.

    One says yes.
  106. decideConsensus • Gives access if the majority says ACCESS_GRANTED (without

    abstainer) • If voters saying ACCESS_GRANTED === voters saying ACCESS_DENIED => the configuration takes the decision security.access_decision_manager.allow_if_equal_granted_denied Majority says yes. default = true
  107. decideUnanimous Gives access if all voters say ACCESS_GRANTED. All of

    them says yes.
  108. If all voters say ACCESS_ABSTAIN the configuration takes the decision.

    security.access_decision_manager.allow_if_all_abstain default = false
  109. decideXxxx //Symfony\Component\Security\Core\Authorization\AccessDecisionManager //… foreach ($this->voters as $voter) { $result[] =

    $voter->vote($token, $object, $attributes); } // logic to return true or false 200 or 403 (forbidden)
  110. Voter MUST NOT be called by you. Voter = Private

    Service
  111. Voter MUST NOT be called by you. Voter = Private

    Service
  112. Voters must be light in case of unanimous or consensus

    strategy. (because all voters are executed)
  113. ALL TOGETHER NOW!

  114. AuthorizationChecker::isGranted()

  115. Controller or template or service from AuthorizationChecker::isGranted()

  116. Controller or template or service from AuthorizationChecker::isGranted() AccessDecisionManager::decide() calls

  117. Controller or template or service from AuthorizationChecker::isGranted() Strategy Voters Decision

    maker if all can’t make a decision Decision maker if all abstain needs AccessDecisionManager::decide() calls
  118. Controller or template or service from AuthorizationChecker::isGranted() Strategy Voters Decision

    maker if all can’t make a decision Decision maker if all abstain needs AccessDecisionManager::decide() calls AccessDecisionManager::decideAffirmative() or AccessDecisionManager::decideConsensus() or AccessDecisionManager::decideUnanimous() calls
  119. Controller or template or service from VoterInterface::vote() … VoterInterface::vote() calls

    AuthorizationChecker::isGranted() Strategy Voters Decision maker if all can’t make a decision Decision maker if all abstain needs AccessDecisionManager::decide() calls AccessDecisionManager::decideAffirmative() or AccessDecisionManager::decideConsensus() or AccessDecisionManager::decideUnanimous() calls
  120. Controller or template or service from VoterInterface::vote() … VoterInterface::vote() calls

    AuthorizationChecker::isGranted() false true returns Strategy Voters Decision maker if all can’t make a decision Decision maker if all abstain needs AccessDecisionManager::decide() calls AccessDecisionManager::decideAffirmative() or AccessDecisionManager::decideConsensus() or AccessDecisionManager::decideUnanimous() calls
  121. WHAT’S NEW IN SYMFONY3?

  122. 2 NEW COMPONENTS

  123. 2 NEW COMPONENTS

  124. Before

  125. Be careful, BC break to expect in 3.1

  126. Tomorrow, 9 am, here!

  127. None
  128. None
  129. security.context security.token_storage security.authorization_checker DEPRECATIONS

  130. DEPRECATIONS intention csrf_token_id

  131. DEPRECATIONS VoterInterface::supportsClass VoterInterface::supportsAttribute

  132. DEPRECATIONS AbstractVoter Voter implement supports($attribute, $subject) and voteOnAttribute($attribute, $subject, TokenInterface

    $token)
  133. DEPRECATIONS The subcomponent ACL has been removed since version 2.8.

  134. None
  135. None
  136. Thank you! @saro0h speakerdeck.com/saro0h/ github.com/saro0h/oauth-github Follow me on @catlannister We

    hire ;)