CSRF protection

CSRF (Cross-Site Request Forgery) is an attack in which a malicious website tricks the users of your web app to perform form submissions without their explicit consent.

To protect against CSRF attacks, you should define a hidden input field holding the CSRF token value that only your website can generate and verify.

Configuration

Any route configured with the web middleware group will automatically have CSRF protection enabled.

If you want to configure a specific route with CSRF protection, you can use the ValidateCSRFToken middleware:

The ValidateCSRFToken middleware requires that a session store has been configured. As such you should also add the LoadSession middleware to the route.

from expanse.session.middleware.load_session import LoadSession
from expanse.session.middleware.validate_csrf_token import ValidateCSRFToken

router.post('/profile', ...).middleware(LoadSession, ValidateCSRFToken)

Protecting forms

As mentioned earlier, you should include a hidden input field in your forms to hold the CSRF token value. This may be done using the csrf_token helper function inside your templated:

<form method="POST" action="/">
  <input type="hidden" name="_token" value="{{ csrf_token() }}">
  <input type="name" name="name" placeholder="Enter your name">
  <button type="submit">Submit</button>
</form>

During the form submission, the ValidateCSRFToken middleware will automatically validate the _token token, only allowing the form submissions with a valid CSRF token.

Handling errors

If the CSRF token validation fails, the ValidateCSRFToken middleware will throw a CSRFTokenMismatchError exception. By default, this exception will be caught by the framework and a 419 Page Expired response will be returned.

If you'd like to customize the error handling, you override the default error handler in your AppServiceProvider:

from expanse.contracts.debug.exception_handler import ExceptionHandler
from expanse.core.http.exceptions import HTTPException
from expanse.session.exceptions import CSRFTokenMismatchError
from expanse.support.service_provider import ServiceProvider


class AppServiceProvider(ServiceProvider):
    async def boot(self, handler: ExceptionHandler) -> None:
        handler.prepare_using(
            CSRFTokenMismatchError,
            self.handle_csrf_token_mismatch
        )

    def handle_csrf_token_mismatch(
        self, error: CSRFTokenMismatchError
    ) -> HTTPException:
        return HTTPException(403, str(error))

Using the X-CSRF-TOKEN header

In addition to checking for the CSRF token as a POST parameter for forms, the ValidateCSRFToken middleware will also check for a X-CSRF-TOKEN request header.

Using the X-XSRF-TOKEN header

Expanse stores the current CSRF token in an encrypted XSRF-TOKEN cookie that is included with each response generated by the framework. This cookie can be read by the frontend JavaScript and used to set the X-XSRF-TOKEN header on subsequent requests.

This allows frontend request libraries like Axios to read the XSRF-TOKEN automatically and set it as a X-XSRF-TOKEN header when making Ajax requests without server-rendered forms.

The XSRF-TOKEN cookie will be encrypted if — and only if — the EncryptCookies middleware is enabled for the route matching the request. This is the default behavior for the web middleware group.