Joseph Estrada
Joseph Estrada

Reputation: 93

How do you add csrf validation to pyramid?

I'm passing in a csrf_token for every post and xhr request and want to validate the token against the session csrf token. If they don't match, I throw a 401.

I've used the NewResponse subscriber in pyramid to inspect the request and validate the csrf token in the request params against the token in the session. The validation works but it still calls the view so it def does not work as it should.

Any suggestions on the proper way to do this?

@subscriber(NewResponse)
def new_response(event):
    """Check the csrf_token if the user is authenticated and the 
    request is a post or xhr req.
    """
request = event.request
response = event.response
user = getattr(request, 'user', None)
# For now all xhr request are csrf protected.
if (user and user.is_authenticated()) and \
   (request.method == "POST" or request.is_xhr) and \
    (not request.params.get('csrf_token') or \
    request.params.get('csrf_token') != unicode(request.session.get_csrf_token())):
    response.status = '401 Unauthorized'
    response.app_iter = []

Upvotes: 9

Views: 4098

Answers (2)

pansen
pansen

Reputation: 371

Pyramid contains its own CSRF validation, which is probably a better choice.

Given your session stored CSRF tokens, this would result in the following configuration:

from pyramid.csrf import SessionCSRFStoragePolicy

def includeme(config):
    # ...
    config.set_csrf_storage_policy(SessionCSRFStoragePolicy())
    config.set_default_csrf_options(require_csrf=True)

Upvotes: 2

Michael Merickel
Michael Merickel

Reputation: 23331

The NewResponse subscriber is called after your view is invoked.

You want to be using an event that is invoked earlier, for example NewRequest or ContextFound. In Pyramid 1.0, you'll need to use ContextFound to properly handle things because you cannot raise exceptions in NewRequest events (this is fixed in 1.1).

The way to do this with a ContextFound event is to register an exception view for HTTPException objects like this:

config.add_view(lambda ctx, req: ctx, 'pyramid.httpexceptions.HTTPException')

Basically this will return the exception as the response object when you raise it, which is perfectly valid for HTTPException objects which are valid Pyramid Response objects.

You can then register your event and deal with the CSRF validation:

@subscriber(ContextFound)
def csrf_validation_event(event):
    request = event.request
    user = getattr(request, 'user', None)
    csrf = request.params.get('csrf_token')
    if (request.method == 'POST' or request.is_xhr) and \
       (user and user.is_authenticated()) and \
       (csrf != unicode(request.session.get_csrf_token())):
        raise HTTPUnauthorized

Upvotes: 10

Related Questions