Access Control
This section explains how to integrate Relay API Access Control into your application.
Policy Configuration
You can add Access Control Policies and Access Control Attributes to your application by appending the respective node definitions to the config tree of your application (see Symfony Documentation for details):
class Configuration implements ConfigurationInterface
{
public const MAY_READ_BLOG_POST = 'MAY_READ_BLOG_POST';
public const MAY_ADD_BLOG_POST = 'MAY_ADD_BLOG_POST';
public const USER_GROUPS = 'USER_GROUPS';
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('my_app');
$treeBuilder->getRootNode()->append(
AuthorizationConfigDefinition::create()
->addPolicy(self::MAY_READ_BLOG_POST, 'false', 'Returns true if the authenticated user may read the given blog post.')
->addPolicy(self::MAY_ADD_BLOG_POST, 'false', 'Returns true if the authenticated user may add the given blog post.')
->addAttribute(self::USER_GROUPS, '[]', 'Returns an array of group IDs the authenticated user is part of')
->getNodeDefinition()
);
return $treeBuilder;
)
The AuthorizationConfigDefinition
class is defined in the Core Bundle.
Its provides the following methods:
public static function create()
Creates a new instance of AuthorizationConfigDefinition
.
public function addPolicy(string $policyName, string $defaultExpression = 'false', string $info = ''): AuthorizationConfigDefinition
$policyName
, with the default expression $defaultExpression
('false'
meaning that nobody is granted access) and the policy description $info
.
public function addAttribute(string $attributeName, string $defaultExpression = 'false', string $info = ''): AuthorizationConfigDefinition
Appends a new node definition for the attribute $attributeName
, with the default expression $defaultExpression
and
the attribute description $info
.
public function getNodeDefinition(): NodeDefinition
Returns the authorization
config node definition to append to the config tree's root node.
The config definition example above yields the following default config:
my_app:
authorization:
MAY_READ_BLOG_POST: 'false'
MAY_ADD_BLOG_POST: 'false'
USER_GROUPS: '[]'
Creating Your Authorization Service
To evaluate your policy and attribute expressions on client requests you need to create a service which derives from the
AbstractAuthorizationService
class declared in the
Core Bundle. Let's call it MyAuthorizationService
:
class MyAuthorizationService extends AbstractAuthorizationService
{}
To set up policy and attribute configuration from your app's config, be sure to call the following lines of code on service load:
class MyAppExtension extends ConfigurableExtension
{
public function loadInternal(array $mergedConfig, ContainerBuilder $container)
{
...
$definition = $container->getDefinition(MyAuthorizationService::class);
$definition->addMethodCall('setConfig', [$mergedConfig]);
...
}
}
And declare MyAuthorizationService
as a service in your services.yaml
file:
MyApp\Authorization\MyAuthorizationService:
autowire: true
autoconfigure: true
Now you are ready to use your access control policies and attributes at runtime, i.e. on client requests:
class MyAppController
{
private $authorizationService;
public function __contruct(MyAuthorizationService $authorizationService)
{
$this->authorizationService = $authorizationService;
}
/**
* @throws ApiError throws a 403 'forbidden' exception if the current user is not authorized to add $blogPost
*/
public function addBlogPost(BlogPost $blogPost)
{
// if you just want to check without an exception being thrown, use $this->authorizationService->isGranted(...)
$this->authorizationService->denyAccessUnlessIsGranted(Configuration::MAY_ADD_BLOG_POST, $blogPost);
// add the blog post
}
public function getUserGroups(): array
{
return $this->authorizationService->getAttribute(Configuration::USER_GROUPS);
}
}
Note that the denyAccessUnlessIsGranted
method gets passed the blog post as a parameter. It is available
in your policy expression as object
variable
(see The Resource Object).
Symfony Access Control (Deprecated)
Assuming you have a "my-scope" that gets somehow mapped into the access token you want to restrict certain APIs and operations to only work if the token has this special scope.
Your implementation can decide to map this to a specific role for the current user, for example:
some-scope -> ROLE_SCOPE_SOME-SCOPE
You can now check for ROLE_SCOPE_SOME-SCOPE
in your bundle.
By default, if no token is passed to the API then you get an anonymous user. To prevent anonymous requests you need to check for a special "role" provided by Symfony:
IS_AUTHENTICATED_FULLY
To restrict the access to certain attributes, operations, entities see the API-Platform documentation and the Symfony documentation.
Examples
Check roles in custom controllers or data providers/persisters:
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
final class MySuperController extends AbstractController
{
public function someFunctionsWhichAccessesTheData(...) {
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$this->denyAccessUnlessGranted('ROLE_SCOPE_FOO');
// ...
}
}
In entity annotations:
Note that the security checks on the operations do not prevent data providers from being called. The checks will be done after the requested objet has been checked. If that is a problem you should check the roles in you data provider as well (or only there, since that is enough)
/**
* Secured resource.
*
* @ApiResource(
* attributes={"security"="is_granted('IS_AUTHENTICATED_FULLY') and is_granted('ROLE_SCOPE_FOO')"},
* collectionOperations={
* "get",
* "post"={"security"="is_granted('IS_AUTHENTICATED_FULLY') and is_granted('ROLE_SCOPE_FOO')"}
* }
*/
class Book
{
// ...
}