So, I'm trying to make a RESTful API in Symfony2, but I'm having an issue with the security.
Let's say, for example, I want to create a new user with my API. I will do the following request:
POST /api/v1/users.json HTTP/1.1
This URL should be accesible by all clients, so there's no authentication required. But, let's say I want to request a list of all the users. According to the REST idea, I should make a GET request:
GET /api/v1/users.json HTTP/1.1
Of course, I don't want this list to be accesible by everyone, so I'll have to secure it in Symfony2. The following won't work, of course, since it secures the entire URL pattern, and not the HTTP method:
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: ^/
anonymous: ~
http_basic:
realm: "Social Portal API"
access_control:
- { path: /api/v1/users.json, roles: ROLE_ADMIN }
So, is there a secret parameter for the access_control
directive that secures the HTTP method? Or is there any other way? I've tried to use the JMSSecurityExtraBundle:
/**
* @Secure(roles="ROLE_ADMIN")
*/
public function listAction()
{
return new Response('Cubilon\\SocialPortal\\APIBundle\\Controller\\UserController', 200);
}
Which should secure this method, but it didn't work...
How can I secure a certain HTTP method in combination with a URL pattern?
EDIT:
So, as I said under the answer below, I've fixed it using the JMSSecurityExtraBundle. I've defined the services I want to secure in Resources/config/services.xml:
# Resources/config/services.xml
<?xml version="1.0" encoding="utf-8"?>
<services>
<service id="user_controller" class="Cubilon\SocialPortal\APIBundle\Controller\UserController">
<tag name="security.secure_service" />
</service>
</services>
And I then secured each action accordingly in the UserController:
# Controller/UserController.php
<?php
namespace Cubilon\SocialPortal\APIBundle\Controller\UserController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use JMS\SecurityExtraBundle\Annotation\Secure;
class UserController extends Controller
{
public function createAction($_format)
{
// ...
}
/**
* @Secure(roles="ROLE_USER, ROLE_ADMIN")
*/
public function readAction($username, $_format)
{
// ...
}
/**
* @Secure(roles="ROLE_USER, ROLE_ADMIN")
*/
public function updateAction($username, $_format)
{
// ...
}
/**
* @Secure(roles="ROLE_USER, ROLE_ADMIN")
*/
public function deleteAction($username, $_format)
{
// ...
}
}
In each secured action I check the credentials of the secured user (whether the authenticated username is the same as the requested username and such).
I know it is late, but if someone stumbles on this questions here is how to secure per a request per HTTP method (see the Symfony security documentation):
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/api/v1/users.json, roles: ROLE_ADMIN, methods: [POST, PUT] }
- { path: /api/v1/users.json, roles: ROLE_ADMIN }
Be careful the order in which you set the rules matters.
According to the security reference book, you can't secure a URL by method.
Not the best way but you can do like that, in the action:
public function listAction(Request $request)
{
if ($request->getMethod() == 'GET' && !$this->get('security.context')->isGranted('ROLE_ADMINISTRATOR')) {
throw $this->createNotFoundException("This page doesn't exist."); // forward a 404, or a message in a json...
}
return new Response('Cubilon\\SocialPortal\\APIBundle\\Controller\\UserController', 200);
}
Or you can create a new kernel event listener that will check the method and the user ROLE like my previous example, but extend to all the actions ! ^^
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With