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 them
security:
...
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..