Code Monkey home page Code Monkey logo

dunglasangularcsrfbundle's Introduction

JavaScript CSRF Protection Bundle

Archived! Now that all modern browsers implement SameSite cookies and the Origin HTTP header, this bundle is - in most cases - not necessary anymore. Learn how to protect your Symfony APIs from CSRF attacks. If you need to maintain old applications, take a look to DneustadtCsrfCookieBundle.

This API Platform and Symfony bundle provides automatic Cross Site Request Forgery (CSRF or XSRF) protection for client-side applications.

Despite the name, it works with any client-side technology including Angular, React, Vue.js and jQuery. Actually, any JavaScript code issuing XMLHttpRequest or using the Fetch API can leverage this bundle.

Build Status SensioLabsInsight Dependency Status StyleCI

How it Works

Thanks to this bundle, the server-side application (the Symfony app) will automatically set a cookie named XSRF-Token containing a unique token during the first HTTP response sent to the browser. Subsequent asynchronous requests made by the JavaScript app with xhr or fetch send back the value of the cookie in a special HTTP header named X-XSRF-Token.

To prevent CSRF attacks, the bundle will check that the header's value match the cookie's value. This way, it will be able to detect and block CSRF attacks.

AngularJS (v1)'s ng.$http service has a built-in support for this CSRF protection system. If you use another framework or HTTP client (such as Axios), you just need to read the cookie value and add the HTTP header containing it by yourself.

This bundle provides a Symfony's Event Listener that set the cookie and another one that checks the HTTP header to block CSRF attacks.

Thanks to DunglasAngularCsrfBundle, you get CSRF security without modifying your code base.

This bundle works fine with both API Platform and FOSRestBundle.

Installation

Use Composer to install this bundle:

composer require dunglas/angular-csrf-bundle

If you use Symfony Flex, you're done.

Otherwise add the bundle in your application kernel:

// app/AppKernel.php

public function registerBundles()
{
    return array(
        // ...
        new Dunglas\AngularCsrfBundle\DunglasAngularCsrfBundle(),
        // ...
    );
}

Configure URLs where the cookie must be set and that must be protected against CSRF attacks:

# app/config/security.yml
dunglas_angular_csrf:
    # Collection of patterns where to set the cookie
    cookie:
        set_on:
            - { path: ^/$ }
            - { route: ^app_, methods: [GET, HEAD] }
            - { host: example.com }
    # Collection of patterns to secure
    secure:
        - { path: ^/api, methods: [POST, PUT, PATCH, LINK] }
        - { route: ^api_v2_ }
        - { host: example.com, methods: [POST, PUT, PATCH, DELETE, LINK] }
    # Collection of patterns to exclude
    exclude:
        - { path: ^/api/exclude, methods: [POST, PUT, PATCH, LINK] }
        - { route: ^api_v2_exclude }
        - { host: exclude-example.com, methods: [POST, PUT, PATCH, DELETE, LINK] }
        

Your app is now secured.

Examples

  • DunglasTodoMVCBundle: an implementation of the TodoMVC app using Symfony, Backbone.js and Chaplin.js

Full Configuration

dunglas_angular_csrf:
    token:
        # The CSRF token id
        id: angular
    header:
        # The name of the HTTP header to check (default to the AngularJS default)
        name: X-XSRF-TOKEN
    cookie:
        # The name of the cookie to set (default to the AngularJS default)
        name: XSRF-TOKEN
        # Expiration time of the cookie
        expire: 0
        # Path of the cookie
        path: /
        # Domain of the cookie
        domain: ~
        # If true, set the cookie only on HTTPS connection
        secure: false
        # Patterns of URLs to set the cookie
        set_on:
            - { path: "^/url-pattern", route: "^route_name_pattern$", host: "example.com", methods: [GET, POST] }
    # Patterns of URLs to check for a valid CSRF token
    secure:
        - { path: "^/url-pattern", route: "^route_name_pattern$", host: "example.com", methods: [GET, POST] }
    # Patterns to exclude from secure routes
    exclude:
        - { path: "^/url-pattern/exclude", route: "^route_name_pattern$", host: "example.com", methods: [GET, POST] }

Integration with the Symfony Form Component

When using the Symfony Form Component together with DunglasAngularCsrfBundle, the bundle will automatically disable the built-in form CSRF protection only if the CSRF token provided by the header is valid.

If no CSRF header is found or if the token is invalid, the form CSRF protection will not be disabled by the bundle.

If you want your form to be validated only by the form component system, make sure to remove its URL from the config.

Credits

This bundle has been created by Kévin Dunglas.

dunglasangularcsrfbundle's People

Contributors

b-galati avatar chrisguitarguy avatar defrag avatar dunglas avatar iambrosi avatar leevigraham avatar marein avatar mateuszsip avatar nd-roy avatar norkunas avatar uriziel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

dunglasangularcsrfbundle's Issues

Provide HttpOnly option

The HttpOnly flag is being set to false in the CookieListener. Please expose HttpOnly as a config option.

SameSite cookie configuration

Hi there,

As you might be aware there are some changes coming to Google Chrome in Feb 2020 related to the SameSite setting to cookies: https://blog.chromium.org/2019/10/developers-get-ready-for-new.html

Currently we hardcode this setting to the 'Lax' value:

This is actually the Chrome default so it makes sense, however things in the wild are a bit more interesting - specifically in my project we need to be able to set this value to 'None'. We have some functionality that relies on iframe being loaded from a different domain for our web app.

Ideally I think this bundle should respect and try to use the 'cookie_samesite' configuration setting that Symfony provides for maximum flexibility. If it's not defined then it would make sense to default it to 'Lax'.

Any feedback on the above would be appreciated.

Thanks,
Aleks

Please make dunglas_angular_csrf.token_manager service public

I'm trying to make your bundle work for GraphiQL interface following documentation

Because dunglas_angular_csrf.token_manager is set to public=false I'm using the following workaround:

<?php

namespace App\Twig;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

class GraphiQLCSRFExtension extends \Twig_Extension
{
    /**
     * @var CsrfTokenManagerInterface
     */
    protected $csrfTokenManager;

    protected $tokenId;

    /**
     * Constructor.
     *
     * @param CsrfTokenManagerInterface $csrfTokenManager
     */
    public function __construct(CsrfTokenManagerInterface $csrfTokenManager, ContainerInterface $container)
    {
        $this->csrfTokenManager = $csrfTokenManager;
        $this->tokenId = $container->getParameter('dunglas_angular_csrf.token.id');
    }

    public function getFunctions()
    {
        return [
            new \Twig_Function('getCSRFToken', function () {
                return $this->csrfTokenManager->getToken($this->tokenId);
            }),
        ];
    }
}

It would be great if my code didn't know about dunglas_angular_csrf.token.id parameter but use the configured service.

The csrf token is rendered when displaying the form

It looks like the CSRF token is disabled for validation when submitting the form but is displayed in the page anyway.
This behaviour prevents us to serialize the whole form before sending it to the server using asynchronous call because the _token field would be included by Symfony when rendering the form.

I have no idea to solve this one but I'll be glad to help if someone can give me a hint.
The first way I can see would be to use a compiler pass based on a tag to disable csrf protection on specific forms.

Using same bundle for login

Hey dude, thanks for the awesome bundle, we are using it right now for a project. Currently we need to make a login through the login_check endpoint, and we are sending the username and password. To obtain the csrf_token for this we are retrieving the login page (app.php/login) and extracting the csrf_token from that form. Is there a way to use this bundle to get the csrf token from the endpoint and then send it with the user credentials?

Thanks in advance!

Mention csrf_protection should be false on formtypes

The documentation does not provide any information about the csrf_protection in the formtypes, and so confuses people about the working of the protection.

Take the following use case:

A developer has a symfony2 application up and running, but keeps having trouble with CSRF validation in combination with the forms. It does a search on Google and finds this bundle. Finally it decides to try it out and does not modify his existing formtypes because it would expect the bundle will solve the CSRF problem, however the forms keep returning invalid CSRF errors. The developer then starts thinking the bundle just does not work (due to version incompatibility?), however if the docs did mention CSRF should be explicitly be disabled on the formtypes it would find out it does work.

Just a small hint ;-)

Missing

Hi, after I upgraded to Symfony 2.7 the Form-extension stopped working. To get it to work again, I had to change the tag definition of dunglas_angular_csrf.form.extension.disable_csrf
from

<tag name="form.type_extension" alias="form"/>
<tag name="form.type_extension" alias="form" extended-type="Symfony\Component\Form\Extension\Core\Type\FormType" />

Also I had to change the returnvalue of getExtendedType from form to FormType::class

I think the changes needed require Symfony 2.7 but I'm not quite sure.

Header Information appears in Body

I am using this great bundle with Symfony 2.7 and fos user bundle.

I started with this config:

dunglas_angular_csrf:
    # Collection of patterns where to set the cookie
    cookie:
        set_on:
            - { path: ^/$ }
            - { route: ^app_, methods: [GET, HEAD] }
            - { host: dev.local }
    # Collection of patterns to secure
    secure:
        - { path: ^/, methods: [POST, PUT, PATCH, LINK] }
#        - { route: ^ }
        - { host: dev.local, methods: [POST, PUT, PATCH, DELETE, LINK] }

I see the correct Response-Header.

However, when I return some json information, I get this in the Response Body as well:

Value: 98iTVMOTBYLfJXT1a70umTjsmMCpCZ7bKAmTXSuDwts, header: X-XSRF-TOKEN{"errors":{"email":"Email already exists"}}

So the Token is in the body and the header which breaks my JSON response.

Am I doing something wrong? I use this as an AJAX requet and in my Controller I do the following:

return new JsonResponse(array('errors' => array('email' => $this->get('translator')->trans('registration.flash.user_email_exists', array(), 'user')) ), 400);
dev.local is my test server I am working on.

Thanks for any help. Best regards

Problem with priority of service "dunglas_angular_csrf.validation_listener"

Hi guys,
just a heads up for everyone, who might have similar issues.
I experienced a problem with the priority of the "dunglas_angular_csrf.validation_listener" today. For some reason the propagation of the "kernel.request" event was stopped by the Firewall "Symfony\Component\Security\Http\Firewall"

event.DEBUG: Listener "Symfony\Component\Security\Http\Firewall::onKernelRequest" stopped propagation of the event "kernel.request".

This ultimately lead to the "AngularCsrfValidationListener" not being triggered at all.
I now fixed this by overwriting the "kernel.event_listener" tag of the service "dunglas_angular_csrf.validation_listener" via a CompilerPassInterface and adding priority 999 in the process.

Did anyone ever experience a similar problem?
How did you solve it?

Config for security.yml

Hello,

I install the bundle but i don't know how i configure /app/config/security.yml

My file :

`security:

encoders:
    FOS\UserBundle\Model\UserInterface: bcrypt

role_hierarchy:
    ROLE_ADMIN:       ROLE_USER
    ROLE_SUPER_ADMIN: ROLE_ADMIN

providers:
    fos_userbundle:
            id: fos_user.user_provider.username

firewalls:

    dev:
        pattern: ^/(_(profiler|wdt|error)|css|images|js)/
        security: false

    main:
        pattern: ^/
        form_login:
            provider: fos_userbundle
    logout: true
    anonymous: true

access_control:
        - { path: ^/api, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/, role: ROLE_ADMIN }`

Thanks for your help

Symfony fails to load if CsrfTokenManager is not loaded

The service "form.registry" has a dependency on a non-existent service "dunglas_angular_csrf.form.extension.disable_csrf" is the error I get. I am working on upgrading an existing 3.3 app to the symfony/flex architecture.

The issue is caused by the fact that the CsrfCompilerPass removes the service if CsrfTokenManager doesn't exist. However, it does not remove the extension from Twig.

Can it work with legacy Application?

Hi @dunglas I'm struggling with this bundle to secure an API using Symfony 4 (Flex) and a legacy application.

I used this guide to bridge the legacy Application with Symfony Sessions.

The session starts with the legacy application the Symfony 4 use session.storage.php_bridge and the session is saved to a Memcached server.

So far I managed to set the XSRF-TOKEN cookie from the Symfony 4 to the legacy cookies, but when I use it on client side the token changed on the server and I got a 403 (Bad CSRF token) when I call my API. Indeed the session id also changed.

Am I missing something ?

Form CSRF Token Disiabling No Longer Works in Symfony 3.2

Looks like its due to a change in the order type extensions are loaded. See symfony/symfony#19790.

The form type extension in this bundle loads before the core csrf extension. So a valid csrf token from this bundle disable csrf protection on a form, only to have it enabled again by the core csrf extension.

I'm not sure if this is a BC break in symfony itself, or something that's a bug (ish, Symfony changing underneath you is not really a bug) in this bundle.

Error in DependencyInjection Configuration

Hi,
When I enable the bundle in Symfony 3 application, I get error in dependency injection configuration:
[Symfony\Component\Config\Definition\Exception\InvalidDefinitionException]
->cannotBeEmpty() is not applicable to NumericNodeDefinition.

Exception trace:
() at .../vendor/symfony/symfony/src/Symfony/Component/Config/Definition/Builder/NumericNodeDefinition.php:71
Symfony\Component\Config\Definition\Builder\NumericNodeDefinition->cannotBeEmpty() at .../vendor/dunglas/angular-csrf-bundle/DependencyInjection/Configuration.php:49
Dunglas\AngularCsrfBundle\DependencyInjection\Configuration->getConfigTreeBuilder() at .../vendor/symfony/symfony/src/Symfony/Component/Config/Definition/Processor.php:50
Symfony\Component\Config\Definition\Processor->processConfiguration() at .../vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Extension/Extension.php:105
Symfony\Component\DependencyInjection\Extension\Extension->processConfiguration() at .../vendor/dunglas/angular-csrf-bundle/DependencyInjection/DunglasAngularCsrfExtension.php:32

Form component

Hello,

i see that there is an integration to disable csrf from form component but it's not released, so the

composer require dunglas/angular-csrf-bundle

downloads only the 0.1.* version. Why is that?:)

Support for Symfony 4

Hi, are there any plans to support Symfony 4? I've already tried it. The code works and the tests will pass. However, the requirement "phpspec/phpspec": "^2.0" does not work with Symfony 4. The next version will be "phpspec/phpspec": "^4.2.3". This requires "php": "^7.0,<7.3" and "symfony/misc-component": "^3.2 || ^4.0". Upgrading this is a breaking change.

Solution 1
Release a new major version for "dunglas/DunglasAngularCsrfBundle" with support for Symfony "^3.4 || ^4.0" and php "^7.0" only.

Solution 2
Continue support for php ">=5.3.3" and Symfony "^2.7 || ^3.0 || ^4.0". The travis build must be extended with an environment variable for the phpspec version.

Both solutions work. What's your opinion on this? I can make a pull request when #46 has been merged with master.

RegEX on Domain or Multiple Domain Configuration

Hi,
how can i set multiple domain on configuration property ? If i had to enable XSRF TOKEN on multiple domain ( example.com, example2.com, example3.com ) i'm not sure if domain supports a regular expression or a list of domains. Actually i'm using a subdomain patter (.domain.com in prod and .domain.localhost in dev) and it works for all subdomains but i want to add another pattern in list (.domain2.com).

Problem with storing cookie in the browser

I can't get the cookie being stored in the browser cookie storage. However when I'm making a "GET" request I can see the cookie coming in the request header like this:

Set-Cookie: XSRF-TOKEN=vtVV3OXgG4lE0qRBM6VvRuxry8P7jC71OP5dXE_smlQ; expires=Thu, 29-Mar-2018 05:43:23 GMT; Max-Age=360000; path=/; domain=angular-app.test

Here is my setup:

dunglas_angular_csrf:
    token:
        id: angular
    header:
        name: X-XSRF-TOKEN
    cookie:
        name: XSRF-TOKEN
        expire: 360000
        path: /
        secure: false
        domain: angular-app.test
        set_on:
            - { path: ^/$ }
            - { route: ^api_$, methods: [GET, HEAD] }
            - { host: symfony-app.test }
    secure:
        - { path: ^/api, methods: [POST, PUT, PATCH, LINK] }
        - { route: ^api$ }
        - { host: symfony-app.test, methods: [POST, PUT, PATCH, DELETE, LINK] }

Response headers:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://angular-app.test
Cache-Control: max-age=0, must-revalidate, private
Connection: keep-alive
Content-Type: application/json
Date: Sun, 25 Mar 2018 02:22:19 GMT
Server: nginx/1.11.8
Set-Cookie: XSRF-TOKEN=nYp_mhb5pVCNNf_-vsWOKMLf-_yB2lvdP5NOX-Rl5uc; expires=Thu, 29-Mar-2018 06:22:19 GMT; Max-Age=360000; path=/; domain=angular-app.test
Transfer-Encoding: chunked

I've also tried to add an extra dot to the domain like this:
.domain: angular-app.test

Angular app runs on: http://angular-app.test
Symfony app runs on: http://symfony-app.test

I'm also using a "nelmio/cors-bundle".

Any idea what it might be?

Missing LICENSE File

The source code refers to a LICENSE file in DisableCsrfExtension.php, however there is no LICENSE file in the repository.

Allow Symfony 5

Please allow support for symfony 5. We can't migrate to 5 without this bundle.

Consider adding a Form Extension for disabling csrf when the class is used

Something like this:

/**
 * Class DisableCSRFExtension
 *
 * @author Grégoire Pineau
 * @author Tarjei Huse (Extended the class to disable csrf on forms)
 * 
 */
class DisableCSRFExtension extends AbstractTypeExtension
{
    private $securityContext;
    /**
     * @var RequestStack
     */
    private $requestStack;
    /**
     * @var AngularCsrfTokenManager
     */
    private $angularCsrfTokenManager;
    /**
     * @var RouteMatcherInterface
     */
    private $routeMatcher;
    /**
     * @var array
     */
    private $routes;
    /**
     * @var
     */
    private $headerName;


    /**
     * @param SecurityContextInterface $securityContext
     * @param RequestStack             $requestStack
     * @param AngularCsrfTokenManager  $angularCsrfTokenManager
     * @param RouteMatcherInterface    $routeMatcher
     */
    public function __construct(SecurityContextInterface $securityContext,
                                RequestStack $requestStack,
                                AngularCsrfTokenManager $angularCsrfTokenManager,
                                RouteMatcherInterface $routeMatcher,
                                array $routes,
                                $headerName
    )
    {
        $this->securityContext = $securityContext;
        $this->requestStack = $requestStack;
        $this->angularCsrfTokenManager = $angularCsrfTokenManager;
        $this->routeMatcher = $routeMatcher;
        $this->routes = $routes;
        $this->headerName = $headerName;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        if (!$this->securityContext->getToken()) {
            return;
        }
        $request = $this->requestStack->getCurrentRequest();
        if (!$this->routeMatcher->match($request, $this->routes)
        ) {
            return;
        }
        $value = $request->headers->get($this->headerName);

        if ($this->angularCsrfTokenManager->isTokenValid($value)) {
            $resolver->setDefaults(array(
                'csrf_protection' => false,
            ));
        }
    }

    public function getExtendedType()
    {
        return 'form';
    }
}

Doesn't work without a session

As the bundle uses Symfony's CsrfTokenManager, which in turn relies on session, it doesn't work without a session. This prevents using it with stateless authentication, like JWT. Would it be possible to provide a CsrfTokenManager that doesn't rely on a session ?

By the way, if I understand AngularJS XSRF protection, the server should just ensure that the header matches the cookie (since both are sent), and maybe that it isn't forged.

Authentication

This bundle does handle the csrf when authenticating with ajax request.

Bad csrf token

Hello,
I 'm having "bad csrf token", in my request header there is nothing about XSRF-TOKEN.
I just installed the bundle with the configuration in the doc and I disabled the csrf protection in the formType.Did I miss anything please???
Regards!

403 error when token expires, but the session is still valid

Steps to Reproduce

  • user gets CSRF token from a request
  • user stays on the page for a very long time (> the timeout set in security.yml)
  • user POSTs a request, gets a 403 as the token has expired

Is this expected behaviour? It would be nice if the token expired but somehow manages to identify that it's still the same session.

Trouble in testing

Hello,

Great bundle, but i'm having issues with the testing, it changes the value of the csrf token at the second instance, seems that doesn't recognise the session id. Could you give some pointers on how to make tests when using this bundle?

regards!

Tag a New Version?

0.1 was tagged in January 2014. There's a lot of new features in master. Can we get a new tag so users don't have to use dev-master in their composer.json?

CSRF cookie expired as soon as it gets created

Hi, first thanks for this library. Since this commit 5479263 we experienced problems cause the cookie session was expiring as soon as we get that from the server. and it has sense as the code in the commit is saying set the expiration time to curent time plus configured expired time, which by default is 0, which in regular symfony2 configuration means, remove the cookie when the browser is closed.

thanks

Host option

Hi,

Why there is no possibility to pass the host on which the cookie would be set? And for secure paths same would be nice to have.

Exclude certain routes

I want to exclude a certain route(s).
Mollie needs to connect to my API and put a payment to a certain status.

Is this possible? I really need an answer for it.

Thanks in advance.

"expire" option in configuration doesn't work

Actually the expire option is used to set a fix time (unix time) in the cookie. In my opinion it would be better to set an expiration time depending actual time

AngularCsrfCookieListener.php :

    $event->getResponse()->headers->setCookie(new Cookie(
        $this->cookieName,
        $this->angularCsrfTokenManager->getToken()->getValue(),
        time() + $this->cookieExpire,
        $this->cookiePath,
        $this->cookieDomain,
        $this->cookieSecure,
        false
    ));

By the way thanks for your bundle :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.