5 years PHP & web commercial experience,
Zend Certified PHP 5.2 (5.3 in progress).
#melbsf2 event co-organiser.
Lover of all things < 141 characters & you can follow me @sammyjarrett.
Ideally, this is you right now:
Maybe some of these could apply to you..
Hopefully after tonight, you'll be able to:
And later in the night... Julien will present FOSUserBundle.
The Symfony2 security component is broken up into two main parts: authentication and authorisation.
app/config/security.yml
.security: encoders: Symfony\Component\Security\Core\User\User: plaintext role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] providers: in_memory: users: user: { password: userpass, roles: [ 'ROLE_USER' ] } admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] } firewalls: secured_area: pattern: ^/secure/ http_basic: realm: "Secured Area" stateless: true
hash_algos()
to display available encoding methodssecurity: encoders: Symfony\Component\Security\Core\User\User: plaintext
Remarks:
Symfony\Component\Security\Core\User\User
: Full class name (including namespace) of the user entity; currently set to use in-memory entityplaintext
: This is the encoder used; sha512 is commonly used; you can write your own.security: ... role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
Remarks:
ROLE_ADMIN: ROLE_USER
: Defines ROLE_ADMIN as being an extension of ROLE_USER.ROLE_SUPER_ADMIN: [ROLE_USER, ...]
: ROLE_SUPER_ADMIN is an extension of multiple roles.ROLE_ALLOWED_TO_SWITCH
: Symfony provides a number of reserved role names to provide special functionality. ROLE_ALLOWED_TO_SWITCH allows a user to swap to another user without having to log in as themsecurity: ... providers: in_memory: users: user: { password: userpass, roles: [ 'ROLE_USER' ] } admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
Remarks:
in_memory
: This is the name of my provider.users
: List of hard-coded username, password and rolesuser: { password: userpass, roles: [ 'ROLE_USER' ] }
: Example of a hard-coded user entry.security: ... firewalls: secured_area: pattern: ^/secure/ http_basic: realm: "Secured Area" stateless: true
Remarks:
secured_area
: The name of my firewall.pattern: ^/secure/
: Regular expression for what URLs are covered.http_basic
: Authentication method in use. Symfony includes HTTP basic & digest, login form, and x.509 certificates. Extensions are available to add other methods.security: encoders: MelbSymfony2\Bundle\SecurityExampleBundle\Entity\User: plaintext ... providers: main: entity: { class: MelbSymfony2\Bundle\SecurityExampleBundle\Entity\User, property: username } ... firewalls: login: pattern: ^/secure/login$ security: false secured_area: pattern: ^/secure/ form_login: check_path: /secure/login_check login_path: /secure/login logout: path: /secure/logout target: /
encoders: MelbSymfony2\Bundle\SecurityExampleBundle\Entity\User: plaintext ... providers: main: entity: { class: MelbSymfony2\Bundle\SecurityExampleBundle\Entity\User, property: username }
Remarks:
entity: { class: ...
: Class name of our Doctrine entity.property: username
: The property used to get the login username. UserInterface requires your class to define a getUsername() method.firewalls: login: pattern: ^/secure/login$ security: false
Remarks:
pattern: ^/secure/login$
: URL to our application's login page.security: false
: Disables further firewall processing for that URL pattern.firewalls: ... secured_area: pattern: ^/secure/ form_login: check_path: /secure/login_check login_path: /secure/login logout: path: /secure/logout target: /
Remarks:
form_login
: Use web form to login.check_path
: URL where the form submits to, to authenticate credentials.login_path
: URL where the login form is, for authentication failures.logout
: Handles users logging out.// namspace & use declarations class SecurityController extends Controller { /** * @Route("/secure/login", name="user_login") * @Template */ public function loginAction() { $request = $this->getRequest(); $session = $request->getSession(); // get the login error if there is one if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR); } else { $error = $session->get(SecurityContext::AUTHENTICATION_ERROR); } return array( 'last_username' => $session->get(SecurityContext::LAST_USERNAME), 'error' => $error, ); } }
/** * @Route("/secure/login", name="user_login") * @Template */
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR); } else { $error = $session->get(SecurityContext::AUTHENTICATION_ERROR); }
return array( 'last_username' => $session->get(SecurityContext::LAST_USERNAME) //... );
{% if error %} <div>{{ error.message }}</div> {% endif %} <form action="{{ path('login_check') }}" method="post"> <label for="username">Username:</label> <input type="text" id="username" name="_username" value="{{ last_username }}" /> <label for="password">Password:</label> <input type="password" id="password" name="_password" /> <input type="submit" name="login" /> </form>
Remarks:
{% if error %}
: If error is present, user has failed authentication.{{ path('login_check') }}
: Form submits to login_check path from security.yml
.We now have a fully functioning authentication system.
Some further thinking...
FOSUserBundle
can help.security: ... access_control: - { path: ^/admin/, roles: ROLE_ADMIN } - { path: ^/admin/administrative-cron, ip: 127.0.0.1 } - { path: ^/cart/checkout, requires_channel: https }
Remarks:
path: ^/...
: A regular expression pattern to match.roles: ...
: Required roles for the user to access this resource.ip: ...
: Required IP address for the user to access this resource.requires_channel: ...
: Required channel to access this resource.// standard namespace & use statements, plus the below: use Symfony\Component\Security\Core\Exception\AccessDeniedException; class SecureController extends Controller public function theLongSecureAction() { if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) { throw new AccessDeniedException(); } // ... } }
Remarks:
$this->get('security.context')->isGranted()
: Check if current user has given role.AccessDeniedException
: will direct user to login page, or throw 403 Forbidden.// standard namespace & use statements, plus the below: use JMS\SecurityExtraBundle\Annotation\Secure; class SecureController extends Controller /** @Secure(roles="ROLE_USER") */ public function shorterSecureAction() { // ... } }
Further reading:
@PreAuthorize
are very powerful.
// standard namespace & use statements class AnotherController extends Controller public function indexAction() { // ... if ($this->get('security.context')->isGranted('ROLE_ADMIN')) { $variableOne = 'some arbitrary value'; } else { $variableTwo = 'something else'; } // ... } }
is_granted()
twig function is duplicate of isGranted()
function in the{% if is_granted('ROLE_USER') %} This text is only shown to users who are logged in. The same can be used for any defined role! {% endif %}
app
has reference to user object.{% if app.user is not empty %} Hello {{ app.user.username }}, welcome back! {% endif %}
Further reading:
app
has context of other super variables.Beyond all that I've covered tonight, there were a few other things that those needing deeper authorisation control should check out...
Security component is still more complex:
But documentation is a problem. If possible, contribute to Documentation on Github.
Lastly, recognize..