View · Index

Weblog

Filtered by date 2026-02-23, 1 - 2 of 2 Postings (all, summary)

Enforcing POST for state-changing requests (require_post)

Created by Gustaf Neumann, last modified by Gustaf Neumann 23 Feb 2026, at 06:28 PM

This recipe shows how to enforce POST for state-changing endpoints and how it fits with CSRF protection in OpenACS.

Motivation

State-changing operations (e.g., create/update/delete actions, membership or role management, administrative tasks) should not be reachable via HTTP GET requests.

GET requests are easy to trigger unintentionally or via social engineering (for example by clicking a link in an email). In modern browsers, session cookies with SameSite=Lax are still sent on cross-site top-level GET navigations, which makes such endpoints vulnerable to CSRF-style attacks if no additional protection is in place.

To reduce this attack surface, OpenACS applications can explicitly require HTTP POST for state-changing requests.

The ::template::require_post validator

The procedure ::template::require_post enforces that the current request uses the HTTP POST method. If the request is issued using any other method (typically GET), it returns a 405 Method Not Allowed response and aborts request processing.

This validator is intended to be used in the -validate blocks of ad_page_contract and ad_form.

Example A: Using require_post in ad_page_contract

ad_page_contract { 
    ... 
} -query { 
    email ... 
} -validate { 
    method { require_post } 
    csrf   { csrf::validate } 
} 
# Page implementation follows 

In this example:

  • require_post ensures that the request cannot be triggered via GET.

  • csrf::validate performs explicit CSRF token validation.

Placing the method check early in the validation phase ensures that invalid requests are rejected before any state-changing logic is executed.

Example B: Using require_post in ad_form

# If the data is not submitted via a POST request, bail out hard. 
# The request might originate from a forged or unintended request. 

ad_form \
    -name myform \
    -form { 
        {email:text {label "E-Mail"}} ... 
    } -validate {
        {email 
            { [require_post] } 
            "Only POST requests are allowed" 
        } 
    } -on_submit { 
        ... 
    } 

This pattern attaches the POST requirement directly to the form validation phase and ensures that the form submission cannot be triggered via a simple GET request.

Caveat: POST is not sufficient by itself

Requiring POST does not replace CSRF protection.

A determined attacker can still trigger cross-site POST requests (for example via auto-submitted forms). Proper CSRF protection therefore must include explicit CSRF token validation.

Requiring POST should be understood as a defense-in-depth measure:

  • It prevents accidental or link-based triggering of state-changing actions.

  • It improves the effectiveness of SameSite=Lax cookies.

  • It reduces the risk of subtle CSRF bugs caused by overlooked GET endpoints.

For full protection, state-changing endpoints should:

  1. Require POST

  2. Validate a CSRF token

  3. (Optionally) perform "Origin" or "Referer" checks for high-risk administrative actions

When to use this pattern

Use require_post for:

  • administrative pages (e.g., under /admin)

  • role or permission changes

  • membership management

  • any operation that modifies server-side state

Do not use require_post for:

  • read-only pages

  • bookmarkable navigation URLs

  • search, filtering, or pagination endpoints

See also

Handling JSON Requests with page contracts

Created by Gustaf Neumann, last modified by Gustaf Neumann 23 Feb 2026, at 05:08 PM

The newest versions of OpenACS (head) and NaviServer (5.1) can now process JSON request bodies in the same convenient way as classic HTML form submissions. When a request is sent with a JSON media type (including structured suffix types such as application/*+json), NaviServer parses the JSON payload automatically and flattens it into the connection form set. This enables using ad_page_contract for validation and makes JSON requests first-class citizens in existing OpenACS applications.

On validation failures, OpenACS can return a standards-based error response using RFC 9457 (application/problem+json) and can include JSON Pointer references (RFC 6901) to pinpoint the offending fields.

Overview

  • Automatic JSON parsing: JSON request bodies are parsed and exposed through ns_getform/ns_conn form as an ns_set.
  • Typed values: The flattened representation includes .type sidecar keys (e.g., user/id.type, user/flags/admin.type) to preserve JSON typing.
  • JSON Pointer addressing (RFC 6901): When reporting errors (and when navigating typed JSON structures via the ns_json triples ensemble), JSON Pointer provides an interoperable path syntax such as /user/flags/admin.
  • Problem Details errors (RFC 9457): Contract violations for JSON requests can return 422 Unprocessable Content with application/problem+json.

Sending JSON Requests

Clients should send JSON with an appropriate Content-Type header:

Content-Type: application/json

Structured syntax suffixes are supported as well:

Content-Type: application/vnd.api+json

Using ad_page_contract with JSON

In OpenACS, you can validate JSON-derived values using ad_page_contract. Because the JSON body is flattened into an ns_set, contract parameter names can refer to nested JSON fields using a slash-separated naming convention.

Example OpenACS page

ad_page_contract {
    test_page
} {
    {user/id:naturalnum 0}
    {user/flags/admin:boolean}
}

ns_return 200 application/json [subst {{"result":"OK", "user_id":${user/id}}}]

The contract declares two inputs:

  • user/id must be a natural number (integer >= 0) and defaults to 0
  • user/flags/admin must be a boolean

Example Request Payload

Submitted JSON data:

{"user": {
    "id":-7,
    "name":"Alice",
    "flags":{
        "admin":"xxx",
        "active":true
    }}
}

Validation Failure Response (RFC 9457 + RFC 6901)

When the request body was parsed as JSON and validation fails, OpenACS can return a Problem Details document (application/problem+json) with status 422. Each error includes a JSON Pointer fragment (#/...) referencing the offending field in the request document.

{
  "type": "https://openacs.org/validation-error",
  "title": "We had a problem with your input:",
  "status": 422,
  "errors": [
    {
      "detail": "user/id is not a natural number, that is an integer greater than or equal to 0.",
      "pointer": "#/user/id"
    },
    {
      "detail": "user/flags/admin does not appear to be a Boolean value.",
      "pointer": "#/user/flags/admin"
    }
  ]
}

Notes

  • The type field is a stable identifier (a URI) for the class of problem. It may be used by clients for automatic handling and may also point to documentation.
  • The title and detail fields are intended for humans and may be localized. Clients should not rely on localized text for program logic.
  • The pointer values use JSON Pointer fragment identifiers (#/...), consistent with the Problem Details ecosystem. The corresponding JSON Pointer path without fragment prefix would be /user/id, /user/flags/admin, etc.

Optional: Typed JSON Navigation with ns_json triples

For applications that need to navigate and update JSON while preserving JSON types (numbers, booleans, null, arrays, objects), NaviServer provides the ns_json triples ensemble. It supports addressing via Tcl list paths or JSON Pointer (RFC 6901) and can generate JSON output directly from triples without lossy conversions. Alternatively of using the ns_set provided by ns_getform, one can get the body of the request and process the included JSON, extract parts of it, etc.

Typical flow:

set t [ns_json parse -output triples $json]
ns_json triples getvalue -pointer /user/flags $t
set t2 [ns_json triples setvalue -pointer /user/flags/admin -type boolean $t true]
ns_json triples getvalue -pretty -pointer /user/flags $t2

See Also

previous February 2026
Sun Mon Tue Wed Thu Fri Sat
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 (2) 23 24 25 26 27 28

Popular tags

17 , 5.10 , 5.10.0 , 5.10.1 , 5.9.0 , 5.9.1 , ad_form , ADP , ajax , aolserver , asynchronous , Azure , bgdelivery , bootstrap , bugtracker , CentOS , COMET , compatibility , conference , CSP , CSRF , cvs , debian , docker , docker-compose , emacs , engineering-standards , exec , fedora , FreeBSD
No registered users in community xowiki
in last 30 minutes
Contributors

OpenACS.org