Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CSRF Protection

Overview

Sukarix provides built-in CSRF (Cross-Site Request Forgery) protection mechanisms to ensure the security of your forms and requests. By enabling CSRF protection, you can safeguard your application from unauthorized actions performed by malicious users.

Protection is enforced automatically: every action extending Sukarix\Actions\Action validates the token in beforeroute() before your handler runs, so you never have to call the validation yourself.

Configuration

CSRF protection is configured in default.ini or the environment-specific config-<environment>.ini. The following default settings enable CSRF protection and set the token expiry time:

[SECURITY]
csrf.enabled = true
csrf.expiry = 3600
  • csrf.enabled: Enables or disables CSRF protection.
  • csrf.expiry: Sets the expiry time for the CSRF token in seconds (default is 3600 seconds or 1 hour).

Protected Verbs

The token is required on every state changing request, that is POST, PUT, DELETE and PATCH. GET, HEAD and OPTIONS requests are never validated.

When validation fails, the request is not passed to your action: it is rerouted to the current path, and form_errors.csrf_token is set in the session so the form can report the problem. A csrf_bounce session message is also set, with a message suitable for display directly to the user (Your form session expired. Please try again.). The rejection is also logged at critical level with the route alias, the verb and the client IP:

Invalid CSRF token {"alias":"login","verb":"POST","ip":"203.0.113.7"}

Tip

A form that silently reloads instead of submitting

Because a rejected request is rerouted rather than answered with an error page, a missing token looks like a form that reloads and does nothing. If a form appears inert, grep your logs for Invalid CSRF token before looking anywhere else.

Adding the Token to a Form

To include a hidden CSRF token input in your forms, use the <csrf/> template directive. This directive generates a hidden input field named csrf_token, ensuring that each form submission includes a valid token.

Example Usage

<form method="post" action="{{ @ALIASES.login }}">
    <csrf/>
    <!-- other form fields -->
    <input type="submit" value="Submit">
</form>

Add the directive to every form that is not a GET form. A form without it cannot be submitted while CSRF protection is enabled.

Sending the Token with AJAX Requests

A hidden field only covers form encoded submissions. Two common cases cannot use one:

  • Requests sending a JSON body, because PHP only populates $_POST for form encoded bodies.
  • PUT, DELETE and PATCH requests, because Fat-Free only maps GET, POST and COOKIE into the hive, so a token sent as a PUT parameter is never readable.

Those requests must send the token in the X-Csrf-Token header instead. Expose the token in your layout:

<meta name="csrf-token" content="{{ @SESSION.csrf_token }}"/>

Then attach it to every state changing request. With jQuery, a single global hook covers the whole application:

$(document).ajaxSend(function (event, jqXHR, settings) {
    if (/^(GET|HEAD|OPTIONS)$/i.test(settings.type)) {
        return;
    }

    // Never disclose the token to another origin
    if (/^(https?:)?\/\//i.test(settings.url) && settings.url.indexOf(window.location.origin) !== 0) {
        return;
    }

    let token = $('meta[name="csrf-token"]').attr('content');
    if (token) {
        jqXHR.setRequestHeader('X-CSRF-Token', token);
    }
});

Header names are case insensitive over HTTP, and Fat-Free normalises them, so the framework always reads the value from HEADERS.X-Csrf-Token. Both names are available as constants:

Sukarix\Core\Session::CSRF_HEADER; // 'X-Csrf-Token'
Sukarix\Core\Session::CSRF_FIELD;  // 'csrf_token'

The header takes precedence over the request parameter, so a request may safely send both.

Token Lifetime

Each session holds a single token, which stays valid until it expires. It is not single use: the same token validates any number of requests within its expiry window. This matters in two everyday cases:

  • A page holding several forms — all of them carry the same valid token.
  • A page performing successive writes without reloading, as an AJAX driven table does.

Within a single request the token is stable, so rendering <csrf/> more than once always yields the same value. Once the token expires, the next request issues a new one.

Note

CSRF Token Expiry

The CSRF token has an expiry time configured in default.ini or the environment-specific config-<environment>.ini. If the token has expired, it will be considered invalid. Ensure your application handles token expiry by prompting the user to refresh the form or session.

Exempting an Endpoint

Endpoints called by external systems — webhooks, telephony gateways, payment callbacks — hold no session and cannot carry a token. Set the $csrfExempt property on those actions:

class Callback extends BaseAction
{
    protected bool $csrfExempt = true;

    public function execute($f3, $params): void
    {
        // ...
    }
}

Warning

An exempt endpoint must authenticate its caller

Exempting an action removes its only CSRF defence. Such an endpoint has to identify the caller by its own means, for instance a shared secret, a request signature, or matching the request against a known host. Never exempt an action that acts on the current user’s session.

Validating the Token Manually

Automatic enforcement is usually enough. When you need the outcome inside a handler, use the isCsrfValid() method from the session. This method reports whether the token submitted with the request matched the token stored in the user’s session.

Example Usage

if ($this->session->isCsrfValid()) {
    // Token is valid, proceed with form processing
} else {
    // Token is invalid, handle the error
}

This code snippet shows how to check the CSRF token when processing a form submission. If the token is valid, you can proceed with the form processing. If the token is invalid, handle the error appropriately.