State-changing web requests often fail in automation even when the username and password are correct because the server expects proof that the request came from the same interactive flow that rendered the form. Reproducing that flow with cURL is often the difference between a working scripted login and a consistent 403 Forbidden response.

Most CSRF protections bind a token to the same response that also establishes or refreshes session cookies. A scripted client therefore has to keep both halves of the exchange aligned: fetch the form or bootstrap response, preserve the cookie jar, extract the token from the returned HTML or JSON, and send the token back in the exact field or header that the application validates.

CSRF tokens are not generic values to replay blindly. Field names, token lifetime, and submission style vary by framework, and synchronized-token patterns should not leak through URLs, logs, or world-readable temp files. The examples below keep the service URLs and account identity masked while preserving the shape of a real login flow, and token-bearing responses plus cookie jars should still stay in a private workspace until verification is complete.

Steps to extract and use CSRF tokens with cURL:

  1. Create a private workspace before downloading any token-bearing files.
    $ umask 077
    $ mkdir -p ~/curl-csrf
    $ cd ~/curl-csrf

    A restrictive umask keeps newly created cookie jars and captured form HTML private to the current account, and a dedicated directory keeps cleanup predictable without exposing temp-path details in the saved transcript.

  2. Define the form, submission, and protected resource URLs that belong to the same authenticated flow.
    $ LOGIN_URL='https://portal.example.net/sign-in'
    $ AUTH_URL='https://portal.example.net/session'
    $ ACCOUNT_URL='https://portal.example.net/account/security'

    Use the exact endpoints the browser flow uses, because many applications issue the token on one URL and validate it on another.

  3. Download the login page and write its session cookies into a jar file.
    $ curl --silent --show-error --location \
      --cookie-jar cookies.txt \
      --output login.html \
      "$LOGIN_URL"

    --cookie-jar writes server-issued cookies after the transfer completes, but it does not read from that file later unless the same path is supplied with --cookie on the follow-up request.

  4. Extract the hidden CSRF field from the saved HTML and stop immediately if it is missing.
    $ TOKEN_FIELD='csrf_token'
    $ TOKEN_VALUE=$(TOKEN_FIELD="$TOKEN_FIELD" perl -0ne '
      my $field = quotemeta $ENV{TOKEN_FIELD};
      if (/<input[^>]+\bname="$field"[^>]+\bvalue="([^"]+)"/s ||
          /<input[^>]+\bvalue="([^"]+)"[^>]+\bname="$field"/s) {
        print $1;
        exit;
      }
    ' login.html)
    $ if [ -z "$TOKEN_VALUE" ]; then
      printf 'Token field %s was not found in %s\n' "$TOKEN_FIELD" login.html >&2
      exit 1
    fi
    $ printf 'Captured %s with %s characters\n' "$TOKEN_FIELD" "${#TOKEN_VALUE}"
    Captured csrf_token with 64 characters

    If the page uses a different field name such as csrfmiddlewaretoken, or injects the token through JavaScript instead of a hidden input, adjust the extraction step to match the real response rather than forcing this HTML pattern.

  5. Read credentials without echoing the password and submit the token with the same cookie jar that fetched the form.
    $ read -r LOGIN_USER
    $ read -sr LOGIN_PASS
    $ printf '\n'
    $ curl --silent --show-error --location \
      --cookie cookies.txt \
      --cookie-jar cookies.txt \
      --data-urlencode "username=$LOGIN_USER" \
      --data-urlencode "password=$LOGIN_PASS" \
      --data-urlencode "$TOKEN_FIELD=$TOKEN_VALUE" \
      --write-out '\nHTTP_STATUS: %{http_code}\n' \
      "$AUTH_URL"
    <!doctype html>
    <html lang="en">
    <head><title>Account security</title></head>
    <body>
      <h1>Account security</h1>
      <p>Signed in as operator@example.net</p>
      <p>Session status: verified</p>
    </body>
    </html>
    
    HTTP_STATUS: 200

    Many applications rotate or add cookies after a successful login, so reusing the same path for --cookie and --cookie-jar keeps the jar current for the next request.

    When the application expects the token in a header such as X-CSRF-Token or X-XSRF-TOKEN instead of a form field, keep the same cookie jar and move the token into --header only after confirming that contract in the form markup or API documentation.

  6. Verify that the authenticated session still works by requesting a protected page with the updated jar.
    $ curl --silent --show-error \
      --cookie cookies.txt \
      --write-out '\nHTTP_STATUS: %{http_code}\n' \
      "$ACCOUNT_URL"
    <!doctype html>
    <html lang="en">
    <head><title>Account security</title></head>
    <body>
      <h1>Account security</h1>
      <p>Signed in as operator@example.net</p>
      <p>Session status: verified</p>
    </body>
    </html>
    
    HTTP_STATUS: 200

    Authenticated content plus HTTP_STATUS: 200 confirms that the session cookie and CSRF token were accepted together during the login flow.

  7. Remove the temporary workspace as soon as the protected request succeeds.
    $ cd ~
    $ rm -rf ~/curl-csrf

    Deleting the workspace removes cookie jars, copied form HTML, and captured token values that otherwise remain reusable on disk after the test.