Design Security in PHP Using Middleware

by Enrico Zimuel
Senior Software Engineer
Rogue Wave Software, Inc.


ZendCon 2017, Las Vegas (NV), Oct. 25

About me

Web security

Securing a web application

  • Filter Input, Escape Output
  • Do not use component with known vulnerabilities
  • Harden your production setup (e.g. use HTTPS)
  • Minimize the usage of critical data and use strong encryption for storing
  • Backup, log and monitor all the things!

Middleware

Middleware in PHP

  • PSR-7: HTTP request and response messages
  • Delegate: turn a request into a response (PSR-15)
  • Middleware: intercepts a request and optionally delegates creation of a response

Delegate Interface


interface DelegateInterface
{
    public function process(
        ServerRequestInterface $request
    ) : ResponseInterface;
}

PSR-15 ^0.4.1

Middleware Interface


interface MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        DelegateInterface $delegate
    ) : ResponseInterface;
}

PSR-15 ^0.4.1

Example


class FooMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        DelegateInterface $delegate
    ) {
        $item = $this->repository->fetchById(
            $request->getAttribute('id')
        );
        return new JsonResponse($item);
    }
}

Security & Middleware

Security bonus

  • Only 1 entry point: PSR-7 request
  • Simple linear workflow, easy to manage
  • Avoid usage of GLOBALS (e.g. $_POST)
  • Usage of immutable HTTP requests
  • Delegating middleware improves the security chain

Expressive 2

Getting Started

Install:
composer create-project zendframework/zend-expressive-skeleton <path-to-install>

Run (localhost:8008):
composer run --timeout=0 serve

Zend-log

Example


use Zend\Log\Logger;

class LoginAction implements ServerMiddlewareInterface
{
    public function __construct(Logger $logger, ...) {
        $this->logger = $logger;
    }

    public function process(
        ServerRequestInterface $request,
        DelegateInterface $delegate
    ) {
        // ...
        $this->logger->log(Logger::INFO, "User $user logged in");
    }
}

Authentication

Basic + PDO config


return [
    'authentication' => [
        'realm' => 'ZendCon2017',
        'pdo' => [
            'dsn' => 'sqlite:database.sq3',
            'table' => 'user',
            'field' => [
                'username' => 'email',
                'password' => 'pwd'
            ]
        ]
    ]
];

Usage


// config/routes.php
use Zend\Expressive\Authentication;
use Zend\Expressive\Helper;

$app->post('/api/admin/posts', [
    Authentication\AuthenticationMiddleware::class,
    Helper\BodyParams\BodyParamsMiddleware::class,
    Book\Action\AddReviewAction::class
], 'admin.posts');

Authorization

RBAC adapter


use Zend\Expressive\Authorization\AuthorizationInterface;
use Zend\Expressive\Authorization\Rbac\ZendRbac;

return [
    'dependencies' => [
        'aliases' => [
            AuthorizationInterface::class => ZendRbac::class
        ]
    ]
];

RBAC config


return [
    'authorization' => [
        'roles' => [
            'administrator' => [],
            'user'          => ['administrator']
        ],
        'permissions' => [
            'user' => [
                'admin.dashboard',
                'admin.posts'
            ],
            'administrator' => [
               'admin.settings',
            ],
        ]
    ]
];

Usage


// config/routes.php
use Zend\Expressive\Authentication;
use Zend\Expressive\Authorization;
use Zend\Expressive\Helper;

$app->post('/api/admin/posts', [
    Authentication\AuthenticationMiddleware::class,
    Authorization\AuthorizationMiddleware::class,
    Helper\BodyParams\BodyParamsMiddleware::class,
    Book\Action\AddReviewAction::class
], 'admin.posts');

Filter & Validation

Example


use Zend\InputFilter\InputFilter;
class Posts extends InputFilter
{
    public function init()
    {
        $this->add([
            'name' => 'title',
            'required' => true,
            'filters' => [
                ['name' => StringTrim::class],
            ],
            'validators' => [
                [
                    'name' => StringLength::class,
                    'options' => ['min' => 1],
                ],
            ],
        ]);
    }
}

Middleware


class ContentValidationMiddleware implements MiddlewareInterface
{
    public function __construct(InputFilter $filter)
    {
        $this->filter = $filter;
    }

    public function process(
        ServerRequestInterface $request,
        DelegateInterface $delegate
    ) {
        $this->filter->setData($request->getParsedBody());
        if (! $this->filter->isValid()) {
            throw Exception\UnValidationException(
                $this->filter->getMessages()
            );
        }
        return $delegate->process(
            $request->withParsedBody($this->filter->getValues())
        );
    }
}

Other PSR-7 tools

Questions?

Thanks!

Rate this talk at https://joind.in/talk/3c288