Software Development

PHP Web Development: Security

3
 min read
Published 
July 2, 2018
3
 min read
Published 
July 2, 2018
PHP Web Development: Security
3
 min read
Published 
July 2, 2018

This is the last article from the custom software development series of posts dedicated to shed a light on modern web application development using PHP and Symfony web framework.

In this article we’ll try to restrict the access to our API and in order to do that we have to regard additional capabilities, provided by the Symfony framework.

Goal

Our goal will be the implementation of the mechanism dedicated to restrict the access to the specific method of the controller. That will be the simplest example of the authorization mechanism.

Let’s try to recall the material from the previous article. As such we already have the controller, that returns the anticipated response to the HTTP request.

Once we skip the business logic - as it isn’t of the importance for our example - the controller looks like:

/**
*@Route ("", name="api-users")
*@Route @Method({"GET"})
*@param Request $request
*@return JsonResponse
*/
public function listAction(Request $request)
{
$array = [];
return new JsonResponse($array);
}

Above the method there is the comments block, all the comments have to be familiar to us. We’ve already discussed that comments of such kind are annotations and because of that they define the additional logic or restrictions - enhance the contract of the controller method.

Let’s introduce our own annotation @GrantAll(). The idea of it is to restrict the invocation of the controller method only if that controller method had the mentioned annotation.

Thus, let’s add the annotation to the comments block:

/**
*@Route ("", name="api-users")
*@Route @GrantAll()* @Method({"GET"})
*@param Request $request
*@return JsonResponse
*/

As for now, the annotation that we have specified is simply the comment and it doesn’t restrict or allows anything yet.

But let’s proceed and make that annotation work. So, we would like to allow controller’s method invocation only if there is the @GrantAll() annotation declared.

How do we go about that?

Symfony has the EventDispatcher component that implements the Mediator pattern and is the central point, where the event can be published by one component and leads to the receiving of that event by other component.

Evenmore, the dispatcher allows for other components to handle the standard Symfony events - and thus to register the specific class / method to listen and handle those.

As the result, we create the class / method with the custom logic and ask dispatcher to call it once the event is emitted.

The comprehensive list of events, emitted by Symfony, can be found in documentation. However, we are more interesting in such event as kernel.controller.  

This event occurs before the controller invocation, any controller, always. Thus, we have to create the Handler for kernel.controller event, then that handler has to check, whether the method of our controller has the corresponding annotation indeed and based on that to take a decision, whether to allow or deny the access.

At first, let’s create the definition of out annotation:

namespace AppBundle \ Annotation;
/**
*@Annotation ("", name="api-users")
*/
class GrantAll
{
}

Nothing complex here, the annotation @Annotation tells that current class is annotation to other. It’s worth to say, that as soon as there is the parameter that is to be defined in annotation, then the declaration of the class above should be a bit more complicated.

Then we have to create the handler, dedicated to check, whether the target method has the corresponding annotation:

./src/AppBundle/EventListener/AuthListener.php

./src/AppBundle/EventListener/AuthListener.php

annotationReader = $annotationReader;
}
/**
*@param FilterControllerEvent $event
*@throws \Exception
*/
public function onKernelController(FilterControllerEvent $event)
{
$controllerInfo = $event->getController();
$object = new ReflectionObject($controllerInfo[0]);
$method = $object->getMethod($controllerInfo[1]);
$annotations = $this->annotationReader->getMethodAnnotations($method);
foreach ($annotations as $annotation) {
if ($annotation instanceof AnnotationGrantAll) {
return;
}
}
$event->setController(function ()
{
return new JsonResponse('Forbidden', 403);
});
} }

We’ve created the AuthListener handler with onKernelController method. Despite the very simple implementation, the class already implements the authorization functionality by listening the required event and reacting to it.

Let’s take a look at the method logic and later we’ll see, how that method becomes to be a handler of the specific event.

So, what does here happens?

We can see that our method has the reference to the controller through the event parameter, passed to it.

Using reflection we get the access to the method annotations and check then, whether the required annotation is declared at method signature.

If the annotation is provided the handler job is done and control is returned back, thus controller’s method is called. If there is no such annotation, then we return the HTTP 403 as the result of method call.

Let’s see, how it looks like in browser:

That simple!

And now let’s understand, what’s the way our method becomes a handler of the required event. We declare that there is a demand for our method to be called before the call of every method of every controller.

We should be already familiar with /app/config folder and the application config files inside of it. For our purposes we are interesting in services block in services.yml config file.

Let’s add couple lines to that file:

app.listener.authorization:
class: AppBundle\EventListener\AuthListener
arguments:
- "@annotation_reader"
tags: - {name: kernel.event_listener, event: kernel.controller,
method: onKernelController, priority: 1 }

The very first line is the arbitrary identifier of our handler inside the config file.

The next line describes the full path to our handler, that is required to listen to the events.

arguments is where we specify, what object is going to be passed as the parameter to the class constructor. @annotation_reader - is the identifier, that is used by Symfony to find the annotation reader service. That is the service provided by ORM Doctrine and it is used to work with all the annotations within the application scope.

And the last one - tags. Here we declare, which events to listen to, what us the priority and the actual method name, that is going to be called.

These few lines will lead the method onKernelController to be executed before every controller method.

The example above can be extended drastically. For example, the annotation can be based on a parameter and that parameter may have the list of the roles / permissions (business related), which define, what permissions user is required to have to call the specific controller method.

And in such a case it’s a proper implementation, when there is a call to DB performed to check, whether the user has the required permissions or not.

Explore the differences between PHP and Node.js.

Summary

Today we have seen the capability to create and use custom annotations in order to implement the authorization. What’s more important, we’ve seen that the custom annotation isn’t something complex to implement.

The applicability of annotations is huge and isn’t limited with the authorization only, but is rather limited with our imagination.

We hope that the series of Axon's articles has shed a light on todays application development using PHP and will help you in your decision making process.

In our next blog post we are talking about CI/CD pipeline and its benefits in boosting the software development processes.

Product Discovery Lab

Free product discovery workshop to clarify your software idea, define requirements, and outline the scope of work. Request for free now.

LEARN more
PDL Slider Illustration
AI PDF Mockup

From Bricks to Bots:
AI in Real Estate

Use cases for PropTech professionals.

Download for free

Software development Team

[1]

No items found.

related cases

[2]

Need estimation?

Leave your contacts and get clear and realistic estimations in the next 24 hours.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
coin image
Estimate Your Mobile App
Take a quick poll and get a clear price estimation

TRY NOW