DEV Community

Cover image for CSRF Protection in WebForms Core Using CodeBehind
Elanat Framework
Elanat Framework

Posted on

CSRF Protection in WebForms Core Using CodeBehind

1. Introduction to CSRF Protection in WebForms Core

Cross-Site Request Forgery (CSRF) is a web security vulnerability in which an attacker tricks a user's browser into sending an unwanted request to a website where the user is authenticated. If the website relies on automatically attached authentication cookies without properly verifying the request, an attacker may be able to trigger actions on behalf of the victim.

WebForms Core (WFC), introduced by Elanat, provides a server-oriented approach to web interface manipulation. The backend generates commands that WebFormsJS executes in the browser, allowing web interfaces to be manipulated through server-generated instructions without requiring a separate frontend framework.

In this article, we will implement a basic CSRF protection mechanism using WebForms Core and CodeBehind, a backend web framework developed by Elanat. We will generate a secure token, store it in a session, add it to an HTML form, and validate it when the form is submitted.

2. CodeBehind as the Backend

CodeBehind handles HTTP requests, manages the session-based CSRF token, validates submitted data, and generates responses through WebForms Core.

The process follows this sequence:

  1. The browser requests the page.
  2. CodeBehind retrieves an existing CSRF token from the session or generates a new one.
  3. WebForms Core adds the token to the HTML form as a hidden input.
  4. The browser submits the form with the token.
  5. CodeBehind compares the submitted token with the token stored in the session.
  6. WebForms Core generates commands to display the validation result.

The security decision takes place on the backend. WebFormsJS executes the resulting interface commands, but it does not determine whether the CSRF token is valid.

3. Generating a Secure CSRF Token

WebForms Core Security

A CSRF token must be unpredictable so that an attacker cannot easily construct a valid request. C# provides RandomNumberGenerator, which is designed for cryptographically secure random number generation.

The following code generates a token from 32 random bytes and converts it into a hexadecimal string:

using System.Security.Cryptography;

string token = Convert.ToHexString(
    RandomNumberGenerator.GetBytes(32));
Enter fullscreen mode Exit fullscreen mode

This produces a 64-character hexadecimal token representing 256 bits of random data.

Unlike a token generated with the ordinary Random class, this value is suitable for security-sensitive purposes. The token should be generated on the server and must not be predictable from public information such as usernames, timestamps, or sequential identifiers.

4. Storing the Token in Session

The token must remain available between the initial page request and the subsequent form submission. In this example, ASP.NET Core Session provides the required storage.

The following method retrieves the existing token or generates and stores one if it does not exist:

private string GetCsrfToken(HttpContext context)
{
    string token = context.Session.GetString("csrf_token");

    if (string.IsNullOrEmpty(token))
    {
        token = Convert.ToHexString(
            RandomNumberGenerator.GetBytes(32));

        context.Session.SetString("csrf_token", token);
    }

    return token;
}
Enter fullscreen mode Exit fullscreen mode

The session allows the server to associate the token with the user's session and retrieve it when the form is submitted. Session services and middleware must be configured correctly in the ASP.NET Core application.

The session cookie should also be protected with appropriate security settings, including HTTPS and suitable cookie attributes.

5. Adding the Token to the HTML Form with WebForms Core

The CSRF token is included in the form as a hidden input. WebForms Core can generate the required interface command without requiring a separate JavaScript operation to create the field.

The example form is:

<form method="POST" action="/security_csrf">
    <label for="email">Email: </label>
    <input type="text" name="email" id="email" />
    <br />
    <button type="submit">Update Email</button>
</form>
Enter fullscreen mode Exit fullscreen mode

In the CodeBehind controller, the token is added to the form using WebForms Core:

WebForms form = new WebForms();

form.AddHidden("<form>", "csrf_token", csrfToken);

Write(form.ExportToHtmlComment());
Enter fullscreen mode Exit fullscreen mode

AddHidden targets the form and adds a hidden field named csrf_token. ExportToHtmlComment() exports the WebForms Core commands in the format expected by the client-side executor.

To execute WebForms Core commands in the browser, the page must also include the WebFormsJS client-side library. For example, if the library is located at /script/web-forms.js, add the following script to the HTML document's <head>:

<script type="module" src="/script/web-forms.js"></script>
Enter fullscreen mode Exit fullscreen mode

Adjust the script path to match the location of the WebFormsJS file in your application.

The browser submits the hidden token along with the other form fields. Although the token is not visible in the interface, it is accessible to the page and must not be treated as a secret from JavaScript executing in that page. Its security depends on its unpredictability and server-side validation, not on the hidden field itself.

Full page management code

public void PageLoad(HttpContext context)
{
    string csrfToken = GetCsrfToken(context);

    if (context.Request.Method == "POST")
    {
        Button_Click(context, csrfToken);
        return;
    }

    WebForms form = new WebForms();

    form.AddHidden("<form>", "csrf_token", csrfToken);

    Write(form.ExportToHtmlComment());
}
Enter fullscreen mode Exit fullscreen mode

6. Validating CSRF Tokens on POST Requests

When the form is submitted, CodeBehind retrieves the token from the request and compares it with the token stored in the session.

The following example checks that both values are non-empty before comparing them:

string formToken =
    context.Request.Form["csrf_token"].ToString();

if (!string.IsNullOrEmpty(csrfToken) &&
    !string.IsNullOrEmpty(formToken) &&
    formToken == csrfToken)
{
    // Continue with the protected operation.
}
else
{
    // Reject the protected operation.
}
Enter fullscreen mode Exit fullscreen mode

Checking both values is important. If the session token is missing or the submitted form does not contain a token, the request must not be accepted.

The == comparison makes the basic validation logic easy to understand. For applications requiring stronger protection against timing-based comparison attacks, a fixed-time comparison can be used instead, as discussed in the final section.

A successful comparison means that the request contains the expected session token. It does not replace authentication, authorization, or validation of the submitted email address.

7. Handling Validation Results with WebForms Core

After validating the token, the controller uses WebForms Core to generate a response that communicates the result to the user.

private void Button_Click(HttpContext context, string csrfToken)
{
    string formToken =
        context.Request.Form["csrf_token"].ToString();

    string email =
        context.Request.Form["email"].ToString();

    WebForms form = new WebForms();

    if (!string.IsNullOrEmpty(csrfToken) &&
        !string.IsNullOrEmpty(formToken) &&
        formToken == csrfToken)
    {
        form.Message(
            "CSRF token validation succeeded.",
            "success");

        form.Message(
            "Your Email is: " + new Security().SafeValue(email));
    }
    else
    {
        form.Message(
            "Security check failed. Please refresh the page and try again.",
            "warning");
    }

    IgnoreAll(); // Transition by bypassing View and Layout
    Write(form.Response());
}
Enter fullscreen mode Exit fullscreen mode

The example uses new Security().SafeValue(email) before including the submitted email in a message. This is intended to prevent untrusted email content from injecting executable HTML or script content into the interface. It is important to use the appropriate output-safety method for the rendering context and avoid encoding the same content twice.

Message generates the interface feedback, while Response() generates the WebForms Core response. IgnoreAll() bypasses the view and layout for this response, allowing the command response to be returned directly.

This demonstrates the separation between security decisions and interface updates: CodeBehind validates the request on the server, WebForms Core generates the commands, and WebFormsJS executes them in the browser.

The application does not have to return an HTTP 403 status solely to display a validation warning. In this command-response workflow, a normal response can preserve Action Control execution. The essential requirement is that the protected operation must never run when validation fails.

8. Additional Security Layers and Conclusion

A session-based CSRF token is an important protection, but it should be part of a broader security strategy.

Fixed-time token comparison

For additional protection, the simple == comparison can be replaced with CryptographicOperations.FixedTimeEquals. This method compares equal-length byte arrays in a way designed to reduce information leakage through timing differences.

For example:

private bool FixedTimeEquals(string a, string b)
{
    if (string.IsNullOrEmpty(a) ||
        string.IsNullOrEmpty(b))
        return false;

    byte[] aBytes = System.Text.Encoding.UTF8.GetBytes(a);
    byte[] bBytes = System.Text.Encoding.UTF8.GetBytes(b);

    if (aBytes.Length != bBytes.Length)
        return false;

    return CryptographicOperations.FixedTimeEquals(
        aBytes, bBytes);
}
Enter fullscreen mode Exit fullscreen mode

The empty-value checks ensure that two missing tokens are never treated as a successful match. The length check ensures that the comparison receives byte arrays of equal length.

Other security measures

Depending on the application, additional measures can include:

  • Origin validation: Check the Origin header against the expected origin for state-changing requests. A carefully implemented Referer check can be used as a fallback when appropriate.
  • SameSite cookies: Configure authentication cookies with an appropriate SameSite policy, such as Lax or Strict, according to the application's requirements.
  • HTTPS: Protect session cookies and requests in transit using HTTPS and suitable secure-cookie settings.
  • Authentication and authorization: Verify that the user is authenticated and permitted to perform the requested operation.
  • Input validation and output safety: Validate submitted data and use Security.SafeValue or another appropriate context-specific output-encoding mechanism when displaying untrusted content.

These measures complement one another. Origin validation and SameSite cookies provide additional layers of defense, but they should not automatically be treated as replacements for CSRF token validation where it is required.

With CodeBehind handling the backend logic and WebForms Core managing server-generated interface commands, CSRF protection fits naturally into a server-oriented web application. The security decision remains on the server, while the resulting feedback is delivered through the WebForms Core response mechanism.

Related links

On Elanat:

On GitHub:

Top comments (1)

Collapse
 
alexshev profile image
Alex Shev •

Clear walkthrough of keeping the CSRF decision on the server while WebFormsJS only applies UI commands. One small hardening detail worth calling out near the equality check is using a fixed-time comparison once both tokens have been decoded to the same representation; that keeps the example aligned with the later security note without changing the session-bound design.