Security Basics

8th November 2011

Sam Jarrett

About me

who is this guy, and how did he get such a cool 'stache?

Sam Jarrett

Open source developer, Symfony contributor & enthusiast.

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.

What's a Security?

Ideally, this is you right now:

Maybe some of these could apply to you..

What's in it for me?

Hopefully after tonight, you'll be able to:

And later in the night... Julien will present FOSUserBundle.

Infrastructure

The Symfony2 security component is broken up into two main parts: authentication and authorisation.

Authentication:

Authorization Authorisation:

Itinerary

1. Authentication

  1. Configuring the Firewall, part 1
    HTTP basic authentication & some hardcoded users
  2. Configuring the Firewall, part 2
    A more real-world use: Using Doctrine & a login form
  3. Expanding our knowledge: Some additional food for thought
    Using multiple Entities, the future of authentication

2. Authorization

  1. Securing a controller
    Restricting URL patterns, functions & altering logic based on user context
  2. Tweaking twig
    Changing template content based on user context
  3. Advanced concepts
    Object-based access control, etc

1.1. Configuring the Firewall, part 1

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

Encoders

security:
	encoders:
		Symfony\Component\Security\Core\User\User: plaintext

Remarks:

Roles

security:
	...
	role_hierarchy:
		ROLE_ADMIN: ROLE_USER
		ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

Remarks:

User Providers

security:
	...
	providers:
		in_memory:
			users:
				user:  { password: userpass, roles: [ 'ROLE_USER' ] }
				admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }

Remarks:

Firewalls

security:
	...
	firewalls:
		secured_area:
			pattern: ^/secure/
			http_basic:
				realm: "Secured Area"
			stateless: true

Remarks:

1.2. Configuring the Firewall, part 2

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: /

Introducing our new user provider!

encoders:
	MelbSymfony2\Bundle\SecurityExampleBundle\Entity\User: plaintext
...
providers:
		main:
			entity: { class: MelbSymfony2\Bundle\SecurityExampleBundle\Entity\User, property: username }

Remarks:

Some new firewall rules

firewalls:
	login:
		pattern: ^/secure/login$
		security: false

Remarks:

Some more new firewall rules

firewalls:
	...
	secured_area:
		pattern: ^/secure/
		form_login:
			check_path: /secure/login_check
			login_path: /secure/login
		logout:
			path: /secure/logout
			target: /

Remarks:

We add a Controller...

// 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,
		);
	}
}

Our Controller in more detail

/**
 * @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)
	//... 
);

Some twig magic + a working login form!

{% 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:

1.3. Expanding our knowledge

We've made it here - well done!

We now have a fully functioning authentication system.

Some further thinking...

2.1. Securing a controller

security.yml Access Control Lists

security:
	...
	access_control:
		- { path: ^/admin/, roles: ROLE_ADMIN }
		- { path: ^/admin/administrative-cron, ip: 127.0.0.1 }
		- { path: ^/cart/checkout, requires_channel: https }

Remarks:

Securing controller actions

// 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:

Securing using the JMS SecurityExtraBundle

// 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:

Securing or changing controller logic

// 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';
		}

		// ...
	}
}

2.2. Tweaking twig

{% 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 %}
{% if app.user is not empty %}
	Hello {{ app.user.username }}, welcome back!
{% endif %}

Further reading:

2.3. Advanced concepts

Beyond all that I've covered tonight, there were a few other things that those needing deeper authorisation control should check out...

Finally...

Security component is still more complex:

But documentation is a problem. If possible, contribute to Documentation on Github.

Lastly, recognize..

  • Big thanks to Flint Interactive for providing our venue tonight & sponsoring our meetup group. <3
  • I've used a javascript based slideshow plugin called Fathom.js, developed by my coworker & buddy @markdalgleish. It's awesome, so go check it out. Seriously.
  • Tweet using #melbsf2 about this pres!