Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Add the security layer to your REST API and serve a distributed web application

Add the security layer to your REST API and serve a distributed web application

In the past editions of the PhpDay we have assisted to several talks about REST APIs and we learned how to implement a proper REST API service. In this talk I want to present how, at Capturator S.r.l., we have added a security layer to our private REST API (based on Symfony 2) adding authentication token and the support for CORS.

I will start with some theoretical and historical facts on Same Origin Policy and I will present the different solutions to deal with it, dwelling on CORS and its W3C recommendation document. CORS is the solution for a web app that needs to communicate with a REST API able to manage all the CRUD verbs in a distributed architecture (different domain or subdomain).
In the second part I will illustrate the actual implementation of the RESTfull API able to manage distributed and authenticated clients. The backend relies on a couple of Symfony 2 useful bundles and a customization of the security layer.
Presentation given at phpDay 2014 (http://2014.phpday.it)

Marco Loche

May 17, 2014
Tweet

More Decks by Marco Loche

Other Decks in Programming

Transcript

  1. Add the security layer to your
    (RESTfull) API and serve a
    distributed web application
    Marco Loche
    PHPDay 2014

    View Slide

  2. PHP is the language Community gives value

    View Slide

  3. 1. Introduction

    View Slide

  4. Who am I ?

    View Slide

  5. About  my  company  

    View Slide

  6. eLearning
    •  Learning Management System
    – Content management system dedicated to
    learning management
     

    View Slide

  7. eLearning
    Sharable Content Object Reference Model
    – SCORM compliant content must be
    transferable on LMS server
    – SCORM compliant content can
    comunicate with LMS
    – SCORM compliant conten must be
    agnostic about server technology
     

    View Slide

  8. How to serve content remotly and on
    different domains keeping control of
    whoever is using it ?

    View Slide

  9. 2. Context

    View Slide

  10. An open system for
    browsing, sharing and
    including resources

    View Slide

  11. API era is now

    View Slide

  12. REST is the best

    View Slide

  13. A matter of consuming resources

    View Slide

  14. Uniform  interface  
    Uniform interface

    View Slide

  15. Richardson Model
    Access through HTTP
    (RMM level 0)
    Endpoints of the api are resource representations
    (RMM level 1)
    Interact with resources with HTTP Verbs and Content
    negotiation
    (RMM level 2)
    … Hypermedia and the glory
    (RMM level 3)

    View Slide

  16.  
     
    Be fluent and
    transitional

    View Slide

  17. REST in Symfony
    jms/serializer-bundle
    friendsofsymfony/rest-bundle
    willdurand/hateoas-bundle
    nelmio/api-doc-bundle

    View Slide

  18. Lesson learned

    View Slide

  19. 3. Managing origin of
    distributed client

    View Slide

  20. Protecting personal data and identity integrity
     
     

    View Slide

  21.  Same Origin Policy

    View Slide

  22. Pages of the same origin can manipulate
    each other's DOM

    View Slide

  23. Content loaded from one domain can not
    interact with content coming from another

    View Slide

  24. Extended to other aspects :
    HTTP Cookies, XMLHttpRequest

    View Slide

  25. Defining origin
    http://tools.ietf.org/html/rfc6454
    trusted === {scheme, host, port}
    not trusted !== {scheme, host, port}

    View Slide

  26. So what’s trustworthy?
    http://www.example.com/index.html
    http://www.example.com/content/page2.html
    http://user:[email protected]/api/resource

    View Slide

  27. And what’s not?
    http://www.example.com/index.html
    http://www.example.com:81/content/page2.html
    https://www.example.com/content/page2.html
    http://example.com/content/page2.html
    http://api.example.com/content/page2.html

    View Slide

  28. We need to relax

    View Slide

  29. Techniques for relaxing
     
    •  document.domain property
    •  Cross-document messaging
    •  JSONP
    •  CORS
     

    View Slide

  30. document.domain property
    http://www.example.com/
    <br/>document.domain = 'example.com';<br/>
    http://store.example.com/
    <br/>document.domain = 'example.com';<br/>

    View Slide

  31. Cross-document messaging
    http://www.example.com/
    http://store.example.net/
    http://example.com/
    <br/>otherWindow.postMessage("hello there!", "http://example.net");<br/>
    http://example.net/
    <br/>window.addEventListener("message",receiveMessage,false);<br/>function receiveMessage(event) {<br/>if (event.origin !== "http://example.com")<br/>// DO STUFF HERE WITH event.data<br/>}<br/>

    View Slide

  32. JSONP
    <br/>

    View Slide

  33. JSONP

    View Slide

  34. Ok, but we want more!
    Previous solutions are valid only if you want to be
    in RMM1
    No Verbs
    No Content Negotiation
    No Headers for security

    View Slide

  35. Cross-Origin Resource Sharing
    •  W3C Recomandation (16 january 2014)
    •  Enable true cross domain communication
    for JavaScript application
    •  Build on top of the XHR

    View Slide

  36. Simple Cross-Origin Request
    JavaScript Code Browser API Server
    xhr.send()
    Actual request
    Actual response
    onload / onerror

    View Slide

  37. Simple Request Method
    HEAD
    GET
    POST
     

    View Slide

  38. Simple request header
    Accept
    Accept-Language
    Content-Type
    Content-Language

    View Slide

  39. Simple response header
    Cache-Control
    Content-Language
    Content-Type
    Expires
    Last-Modified
    Pragma

    View Slide

  40. GET /resource HTTP/1.1
    Origin: http://www.example.com
    Host: api.example.com
    ...
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: http://www.example.com
    ...

    View Slide

  41. Cross-Origin Request with Prefilght
    JavaScript Code Browser API Server
    xhr.send()
    Actual request
    Actual response
    onload / onerror
    Prefligth request
    Preflight response

    View Slide

  42. •  CORS with preflight request are originated by :
    –  Other HTTP verbs (PUT PATCH DELETE)
    –  Other Content-Type (Application/Json)
    –  Custom Headers (Allow-Header : X-AUTH)
    –  Non simple header expose (Expose-Header)
    •  Preflight request + actual request

    View Slide

  43. OPTIONS /resource HTTP/1.1
    Origin: http://www.example.com
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: X-Custom-Header
    Host: api.example.com
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: http://www.example.com
    Access-Control-Allow-Methods: GET, POST, PUT, PATCH,
    DELETE
    Access-Control-Allow-Headers: X-Custom-Header
    Access-Control-Max-Age: 3600
    ...

    View Slide

  44. PUT /cors HTTP/1.1
    Origin: http://www.example.com
    Host: api.example.com
    X-Custom-Header: value
     
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: http://www.example.com
    Content-Type: text/html; charset=utf-8

    View Slide

  45. Implementation
    Client side:
    •  Nicolas Zakas’s Request Object Wrapper
    •  jQuery
    Server side:
    •  NelmioCorsBundle

    View Slide

  46. composer.json
    "require": {
    "nelmio/cors-bundle": "~1.0"},
    }

    View Slide

  47. app/config/config.yml
    nelmio_cors:
    defaults:
    allow_credentials: false
    allow_origin: [*]
    allow_methods: ['GET','PUT','POST']
    expose_headers: []
    max_age: 3600
    hosts: []

    View Slide

  48. app/config/config.yml
    nelmio_cors:
    defaults:
    allow_credentials: false
    allow_origin: [*]
    allow_methods: ['GET','PUT','POST']
    expose_headers: []
    max_age: 3600
    hosts: []

    View Slide

  49. app/config/config.yml
    nelmio_cors:
    defaults:
    allow_credentials: false
    allow_origin: [*]
    allow_methods: ['GET','PUT','POST']
    expose_headers: []
    max_age: 3600
    hosts: []

    View Slide

  50. app/config/config.yml
    nelmio_cors:
    defaults:
    allow_credentials: false
    allow_origin: [*]
    allow_methods: ['GET','PUT','POST']
    expose_headers: []
    max_age: 3600
    hosts: []

    View Slide

  51. […]
    paths:
    '^/api/':
    allow_origin: ['*']
    allow_methods: ['GET','PUT','POST']
    '^/api/sensitiveResource':
    allow_origin:['https://trusted.io']

    View Slide

  52. Lesson learned
    If you want to be RESTfull you have to go with CORS

    View Slide

  53. 4. Managing  security  of  your  
    resources  

    View Slide

  54. Authentication
    •  Basic authentication
    •  Digest authentication
    •  Token authentication
    •  API Key
    •  WSSE
    •  OpenID

    View Slide

  55. Authorization
    •  Authorization token
    •  OAuth 1.0a
    •  OAuth 2

    View Slide

  56. API Security in Symfony
    hwi/HWIOAuthBundle
    friendsofsymfony/oauth-server-bundle
    escapestudios/EscapeWSSEAuthenticationBundle

    View Slide

  57. HANDS ON

    View Slide

  58. https://www.university.edu
    0. authentication
    Web Application Client
    LMS
    SCORM
    1. API Key
    https://auth.example.com
    2 request
    authorization
    https://content.example.com
    3 3 send token
    4 access resources

    View Slide

  59. Custom Authentication Provider

    View Slide

  60. app/configu/security.yml
    security:
    providers:
    [...]
    capturator:
    id: capturator.security.tokenAuth
    in_memory:
    memory:
    users:
    authentication-authority:
    - password: %auth_authority_pwd%
    - roles: ROLE_AUTH'

    View Slide

  61. app/configu/security.yml
    security:
    providers:
    [...]
    capturator:
    id: capturator.security.tokenAuth
    in_memory:
    memory:
    users:
    authentication-authority:
    - password: %auth_authority_pwd%
    - roles: 'ROLE_AUTHENTICATION_AUTHORITY'

    View Slide

  62. firewalls:
    capturator_api_licenser:
    pattern: ^/api/token(.*)
    anonymous: ~
    http_basic:
    realm: "Secured Authentication Authority Realm"
    provider: in_memory
    capturator_api:
    pattern: ^/api/(.*)
    capturator: true
    stateless: true
    [...]
    access_control:
    [...]
    - { path: ^/api/token(.*), roles: ROLE_AUTH, ip: 192.168.0.1, requires_channel: https }

    View Slide

  63. Capturator/CoreBundle/Resources/config/services.xml



    Capturator\CoreBundle\Security\User\CapturatorUserProvider

    Capturator\CoreBundle\Security\Service\TokenRetriever
    [...]


    class="%capturator.security.userProvider.class%">


    [...]

    View Slide

  64. Capturator/CoreBundle/Resources/config/services.xml



    Capturator\CoreBundle\Security\User\CapturatorUserProvider

    Capturator\CoreBundle\Security\Service\TokenRetriever
    [...]


    class="%capturator.security.userProvider.class%">


    [...]

    View Slide

  65. class CapturatorListener implements ListenerInterface
    {
    protected $securityContext;
    protected $authenticationManager;
    protected $tokeRetriever;
    […]
    public function handle(GetResponseEvent $event)
    {
    $request = $event->getRequest();
    $requestTokenString = $this->tokeRetriever->retrieve($request);
    $token = new CapturatorToken();
    $token->setUser($requestTokenString);
    $authToken = $this->authenticationManager->authenticate($token);
    $this->securityContext->setToken($authToken);
    }
    }

    View Slide

  66. class CapturatorListener implements ListenerInterface
    {
    protected $securityContext;
    protected $authenticationManager;
    protected $tokeRetriever;
    […]
    public function handle(GetResponseEvent $event)
    {
    $request = $event->getRequest();
    $requestTokenString = $this->tokeRetriever->retrieve($request);
    $token = new CapturatorToken();
    $token->setUser($requestTokenString);
    $authToken = $this->authenticationManager->authenticate($token);
    $this->securityContext->setToken($authToken);
    }
    }

    View Slide

  67. namespace Capturator\CoreBundle\Security\Service;
    use Symfony\Component\HttpFoundation\Request;
    use Capturator\CoreBundle\Exception\AuthenticationException;
    class TokenRetriever implements TokenRetrieverInterface
    {
    public function retrieve(Request $request)
    {
    if (!$request->query->has('token') && !$request->headers->has('x-cassys-auth')) {
    throw new AuthenticationException(
    ‘Missing required authentication parameter',
    __CLASS__);
    }
    $requestTokenString = $request->query->has('token') ?
    $request->query->get('token') :
    $request->headers->get('x-cassys-auth');
    return $requestTokenString;
    }
    }

    View Slide

  68. class CapturatorListener implements ListenerInterface
    {
    protected $securityContext;
    protected $authenticationManager;
    protected $tokeRetriever;
    […]
    public function handle(GetResponseEvent $event)
    {
    $request = $event->getRequest();
    $requestTokenString = $this->tokeRetriever->retrieve($request);
    $token = new CapturatorToken();
    $token->setUser($requestTokenString);
    $authToken = $this->authenticationManager->authenticate($token);
    $this->securityContext->setToken($authToken);
    }
    }

    View Slide

  69. Capturator/CoreBundle/Security/User/CapturatorUserProvider.php
    class CapturatorUserProvider implements UserProviderInterface
    {
    protected $em;
    public function __construct(EntityManager $em)
    {
    $this->em = $em;
    }
    public function loadUserByUsername( $token) [...]
    public function refreshUser(UserInterface $user ) [...]
    public function supportsClass($class)
    {
    return ($class == "Capturator\CoreBundle\Security\User\CapturatorUser");
    }
    }

    View Slide

  70. The SimplePreAuthenticatorInterface
    interface was introduced in Symfony 2.4.

    View Slide

  71. Extendig NelmioCorsBundle
    Associating Origin and Authorization

    View Slide

  72. /Users/marco/Progetti/Cassys-Server/app/config/config.yml
    capturator_cors:
    allow_origin_entity: CapturatorCoreBundle:CustomerDomainName
    defaults:
    allow_credentials: false
    expose_headers: []
    max_age: 0
    hosts: []
    paths:
    '^/api/3/':
    allow_headers: ['X-Cassys-Auth', 'X-Requested-With', 'Content-Type']
    allow_methods: ['POST', 'PUT', 'GET']

    View Slide

  73. /Users/marco/Progetti/Cassys-Server/app/config/config.yml
    capturator_cors:
    allow_origin_entity: CapturatorCoreBundle:CustomerDomainName
    defaults:
    allow_credentials: false
    expose_headers: []
    max_age: 0
    hosts: []
    paths:
    '^/api/3/':
    allow_headers: ['X-Cassys-Auth', 'X-Requested-With', 'Content-Type']
    allow_methods: ['POST', 'PUT', 'GET']

    View Slide

  74. Capturator/CoreBundle/Resources/config/services.xml



    Capturator\CorsBundle\Options\CustomerDomainProvider


    class="%capturator_cors.customer_origin_provider%">
    %capturator_cors.map%
    %capturator_cors.defaults%
    %capturator_cors.allow_origin_entity%





    View Slide

  75. Capturator/CoreBundle/Resources/config/services.xml



    Capturator\CorsBundle\Options\CustomerDomainProvider


    class="%capturator_cors.customer_origin_provider%">
    %capturator_cors.map%
    %capturator_cors.defaults%
    %capturator_cors.allow_origin_entity%





    View Slide

  76. Capturator/CorsBundle/Options/CustomerDomainRepositoryInterface.php
    namespace Capturator\CorsBundle\Options;
    interface CustomerDomainRepositoryInterface {
    /**
    * @param $origin
    * @return mixed
    */
    public function findOneByOriginDomain( $origin, $token);}

    View Slide

  77. CorsBundle/Options/CustomerDomainProvider.php
    [...]use Nelmio\CorsBundle\Options\ProviderInterface;
    class CustomerDomainProvider implements ProviderInterface
    {
    [...]
    public function getOptions(Request $request)
    {
    [...]
    $origin = $request->headers->get('Origin');
    $token= $request->headers->get('X-CUSTOM-AUTH');
    if (!is_null($this->repository->findOneByOriginDomain($origin, $token))) {
    $options['allow_origin'] = array($origin);
    $options= array_merge($this->defaults, $options);
    return $options;
    }
    }
    }

    View Slide

  78. CorsBundle/Options/CustomerDomainProvider.php
    [...]use Nelmio\CorsBundle\Options\ProviderInterface;
    class CustomerDomainProvider implements ProviderInterface
    {
    [...]
    public function getOptions(Request $request)
    {
    [...]
    $origin = $request->headers->get('Origin');
    $token= $request->headers->get('X-CUSTOM-AUTH');
    if (!is_null($this->repository->findOneByOriginDomain($origin, $token))) {
    $options['allow_origin'] = array($origin);
    $options= array_merge($this->defaults, $options);
    return $options;
    }
    }
    }

    View Slide

  79. 5. Conclusion

    View Slide

  80. Work done

    View Slide

  81. Prefer open protocol

    View Slide

  82. Access control

    View Slide

  83. References
    h7p://www.ics.uci.edu/~fielding/pubs/[email protected]/top.htm  
    h7ps://www.owasp.org/index.php/REST_Security_Cheat_Sheet#[email protected]  
    h7p://www.w3.org/TR/cors/  
    h7p://www.iana.org/assignments/link-­‐[email protected]/  
    h7ps://www.oasis-­‐open.org/commi7ees/wss/documents/WSS-­‐Username-­‐02-­‐0223-­‐merged.pdf  
    h7p://oauth.net/  
    h7p://www.ieP.org/rfc/rfc2617.txt  
    h7p://www.ieP.org/rfc/rfc5849.txt  (OAuth  1.0a)  
    h7p://www.ieP.org/rfc/rfc6749.txt  (OAutn  2.0)  
    h7p://tools.ieP.org/html/dra[-­‐kelly-­‐json-­‐hal-­‐06  
    h7p://www.w3.org/TR/json-­‐ld/  

    View Slide

  84. Readings
    h7p://en.wikipedia.org/wiki/Cross-­‐origin_resource_sharing  
    h7ps://code.google.com/p/browsersec/wiki/Part2#Standard_browser_security_features  
    h7p://www.html5rocks.com/en/tutorials/cors/  
    h7p://enable-­‐cors.org/  
    h7p://williamdurand.fr/2012/08/02/rest-­‐apis-­‐with-­‐symfony2-­‐the-­‐right-­‐way/  
    h7p://welcometothebundle.com/symfony2-­‐rest-­‐api-­‐the-­‐best-­‐2013-­‐way/  
    h7p://2012.phpday.it/talk/designing-­‐h7p-­‐interfaces-­‐and-­‐resPul-­‐web-­‐services/  
    h7p://2013.phpday.it/talk/rest-­‐apis-­‐made-­‐easy-­‐with-­‐symfony2/  
    h7p://www.slideshare.net/dlondero/rest-­‐in-­‐[email protected]­‐27335543  
    h7p://edu.williamdurand.fr/web-­‐security-­‐101-­‐slides/#/  
    h7p://edu.williamdurand.fr/security-­‐slides/#slide1  
    h7p://friendsofsymfony.github.io/slides/[email protected]­‐with-­‐symfony2.html#/  
    h7p://symfony.com/doc/current/cookbook/security/[email protected]@on_provider.html  
    h7p://symfony.com/doc/current/cookbook/security/[email protected]@on.html  

    View Slide

  85. Thanks
    http://joind.in/11303
    @netamorfose
    www.marcoloche.com
    [email protected]
    All pictures are mine

    View Slide

  86. Q & A
    http://joind.in/11303
    @netamorfose
    www.marcoloche.com
    [email protected]
    All pictures are mine

    View Slide