If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
I do use access controls to lock down big sections, but, mostly, I handle authorization inside my controllers.
Let's play around: comment out the access_control
:
... lines 1 - 2 | |
security: | |
... lines 4 - 33 | |
access_control: | |
# - { path: ^/admin, roles: ROLE_ADMIN } |
And open up GenusAdminController
. To check if the current user has a role,
you'll always use one service: the authorization checker. It looks like this:
if (!$this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')
. So,
if we do not have ROLE_ADMIN
, then throw $this->createAccessDeniedException()
:
... lines 1 - 13 | |
class GenusAdminController extends Controller | |
{ | |
... lines 16 - 18 | |
public function indexAction() | |
{ | |
if (!$this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) { | |
throw $this->createAccessDeniedException('GET OUT!'); | |
} | |
... lines 24 - 31 | |
} | |
... lines 33 - 84 | |
} |
That message is just for us developers.
Head back and refresh. Access denied!
So what's the magic behind that createAccessDeniedException()
method? Find out:
hold Command
and click
to open it. Ah, it literally just throws a special exception
called AccessDeniedException
:
... lines 1 - 21 | |
use Symfony\Component\Security\Core\Exception\AccessDeniedException; | |
... lines 23 - 38 | |
abstract class Controller implements ContainerAwareInterface | |
{ | |
... lines 41 - 257 | |
/** | |
* Returns an AccessDeniedException. | |
* | |
* This will result in a 403 response code. Usage example: | |
* | |
* throw $this->createAccessDeniedException('Unable to access this page!'); | |
* | |
* @param string $message A message | |
* @param \Exception|null $previous The previous exception | |
* | |
* @return AccessDeniedException | |
*/ | |
protected function createAccessDeniedException($message = 'Access Denied.', \Exception $previous = null) | |
{ | |
return new AccessDeniedException($message, $previous); | |
} | |
... lines 274 - 396 | |
} |
It turns out - no matter where you are - if you need to deny access for any reason, just throw this exception. Symfony handles everything else.
Simple, but that was too much work. So, you'll probably just do this instead:
$this->denyAccessUnlessGranted('ROLE_ADMIN')
:
... lines 1 - 13 | |
class GenusAdminController extends Controller | |
{ | |
... lines 16 - 18 | |
public function indexAction() | |
{ | |
$this->denyAccessUnlessGranted('ROLE_ADMIN'); | |
... lines 22 - 29 | |
} | |
... lines 31 - 82 | |
} |
Much better: that does the same thing as before.
And, I have another idea! If you love annotations, you can use those to deny
access. Above the controller, add @Security()
then type a little expression:
is_granted('ROLE_ADMIN')
:
... lines 1 - 7 | |
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; | |
... lines 9 - 14 | |
class GenusAdminController extends Controller | |
{ | |
/** | |
* @Route("/genus", name="admin_genus_list") | |
* @Security("is_granted('ROLE_ADMIN')") | |
*/ | |
public function indexAction() | |
{ | |
... lines 23 - 29 | |
} | |
... lines 31 - 82 | |
} |
This has the exact same effect - it just shows us a different message.
But no matter how easy we make it, what we really want to do is lock down this
entire controller. Right now, we could still go to /admin/genus/new
and have
access. We could repeat the security check in every controller... or we could do
something cooler.
Add the annotation above the class itself:
... lines 1 - 7 | |
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; | |
... lines 9 - 11 | |
/** | |
* @Security("is_granted('ROLE_ADMIN')") | |
* @Route("/admin") | |
*/ | |
class GenusAdminController extends Controller | |
{ | |
... lines 18 - 82 | |
} |
As soon as you do that, all of these endpoints are locked down.
Sweet!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.1.*", // v3.1.4
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.4
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
"doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0", // v3.1.3
"nelmio/alice": "^2.1", // 2.1.4
"doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
}
}