Most vulnerabilities are the result of bad coding habits or lack of PHP application security awareness among developers. The primary cause is the fact that user input is treated as trusted.
When you write code, you must apply two key procedures: validation and sanitization. If you implement both these procedures for user data, you ensure that what is processed and executed is valid and meets specified criteria. You must also ensure that the HTML output data is escaped so that no malicious code is executed in case an attacker injected it into the content. If you follow certain simple and basic procedures for every web page, you significantly minimize the possibility of being exposed to a critical security issue.
Protecting your data.php files from direct access, CSRF attacks, session abuse, and denial-of-service — the right way.
A common pattern in web development is a front-end page that fires an Ajax request to a PHP file in order to fetch data. Simple enough — but that PHP endpoint is exposed to the entire internet. Without the right protections, anyone can hit it directly, forge requests on behalf of your logged-in users, or flood it with automated calls.
This guide walks through every layer of defense you should add, in order of importance.
Threat 1 Direct URL access
Threat 2 CSRF attacks
Threat 3 Redirect bypass
Threat 4 DoS / request flooding
1. Use Sessions to Gate Access
The first question to ask is: should only logged-in users get this data? If yes, the simplest guard is a PHP session check. When the user authenticates (username + password), store a session value. Every protected endpoint then checks for it.
Best practice: Always use PHP sessions for authentication — not cookies. Cookies can be manipulated client-side; server-managed sessions are significantly harder to forge.
// At the very top of every protected PHP file:
session_start();
if (!isset($_SESSION['user_id'])) {
header("Location: https://yoursite.com/login");
die();
}
The call to session_start() must be the very first line — before any output. Some servers will throw an error if it appears anywhere else. Combine it with a redirect and always follow that redirect with die() (see section 3 for why).
2. Convert HTML to PHP and Add a CSRF Token
Cross-Site Request Forgery (CSRF) is an attack where a malicious page — opened by one of your logged-in users — silently fires a request to your site using their credentials. Because the browser automatically sends session cookies, your server sees it as a legitimate request.
The defence is a CSRF token: a random value that only your own page knows. The attacking page can't read it, so it can't include it in a forged request.
Step 1 — Rename your html file to .php
Your Ajax-calling front-end page needs to run PHP to generate the token. Rename get-data.html → get-data.php.
Step 2 — Generate and store the token
// get-data.php (top of file)
session_start();
$csrf = bin2hex(random_bytes(32));
$_SESSION['csrf'] = $csrf;
Step 3 — Send the token with your Ajax request
// JavaScript / jQuery
$.ajax({
url: 'data.php',
type: 'POST', // always POST, never GET
data: {
name: 'bob',
csrf: ''
},
success: function(res) { $('#output').html(res); },
error: function(err) { $('#output').html(err.responseText); }
});
Security note: Never use GET to send sensitive data. GET parameters appear in server logs, browser history, and referrer headers. Always use POST for any request that carries tokens or user data.
Step 4 — Validate the token in data.php
// data.php
session_start();
if (
!isset($_POST['csrf']) ||
$_POST['csrf'] !== $_SESSION['csrf']
) {
header("Location: https://yoursite.com");
die();
}
// Safe to serve data below this line
The condition checks two things: that the token was sent at all, and that its value matches the one stored in the server-side session. A forged request will fail both checks.
3. Always Call die() After a Redirect
PHP's header("Location: ...") sets a redirect response, but it does not stop the script from running. An attacker using a tool like curl or a custom browser can simply ignore the redirect header and read all the output that follows.
// WRONG — code below this line still executes
header("Location: https://yoursite.com");
// CORRECT — execution stops immediately
header("Location: https://yoursite.com");
die();
This is one of the most commonly forgotten PHP security details. Always pair every redirect with a die().
4. Also Block Non-POST Requests
For extra hardening, reject any request that doesn't arrive via POST. Someone visiting the URL directly in a browser sends a GET request — this extra check stops them before the CSRF validation even runs.
if (
!isset($_POST['csrf']) ||
$_POST['csrf'] !== $_SESSION['csrf'] ||
$_SERVER['REQUEST_METHOD'] !== 'POST'
) {
header("Location: https://yoursite.com");
die();
}
Case sensitivity: PHP constants like 'POST' are uppercase. Writing 'post' or 'Post' will silently fail the check. The same goes for superglobal keys — $_POST, $_SESSION, and $_SERVER are all uppercase.
5. Rate-Limit Requests with a Session Counter
Even with CSRF protection, a logged-in user — or an attacker who has stolen a valid session — can replay the same Ajax request thousands of times. This is a form of denial-of-service (DoS). A session-based counter lets you cap how many times a single session can call the endpoint.
// After CSRF validation passes:
if (!isset($_SESSION['dos_count'])) {
$_SESSION['dos_count'] = 0;
}
if ($_SESSION['dos_count'] >= 3) {
die('Too many requests. Please wait.');
}
// Increment the counter for each successful call
$_SESSION['dos_count']++;
// ... serve your data here ...
You can extend this with a time-based reset — store a timestamp in the session alongside the counter, then reset the counter once a certain number of seconds has elapsed:
$now = time();
if (!isset($_SESSION['dos_time'])) {
$_SESSION['dos_time'] = $now;
}
// Reset after 60 seconds
if ($now - $_SESSION['dos_time'] > 60) {
$_SESSION['dos_count'] = 0;
$_SESSION['dos_time'] = $now;
}
Note on scope: Session-based rate limiting only protects against a single session flooding the endpoint. For protection against distributed attacks (DDoS), you need server-level solutions such as a WAF, Cloudflare, or nginx rate limiting — those are outside the scope of PHP alone.
Putting It All Together
Here is a complete, annotated data.php that implements every layer described above:
// data.php — fully hardened Ajax endpoint
session_start(); // must be first line
// 1. Reject if not a POST request
// 2. Reject if CSRF token missing or mismatched
if (
$_SERVER['REQUEST_METHOD'] !== 'POST' ||
!isset($_POST['csrf']) ||
$_POST['csrf'] !== $_SESSION['csrf']
) {
header("Location: https://yoursite.com");
die(); // always kill after redirect
}
// 3. Rate limiting
if (!isset($_SESSION['dos_count'])) {
$_SESSION['dos_count'] = 0;
}
if ($_SESSION['dos_count'] >= 3) {
die('Request limit reached.');
}
$_SESSION['dos_count']++;
// 4. Your actual data logic goes here
echo "Hello, " . htmlspecialchars($_POST['name']);
Quick Checklist
- 1Start every protected PHP file withsession_start()on the first line.
- 2Use PHP sessions for auth — not cookies.
- 3Generate a per-page CSRF token withrandom_bytes()and store it in the session.
- 4Send that token via Ajax usingPOST(neverGET).
- 5In the PHP endpoint, verify the token matches the session value.
- 6Always calldie()immediately afterheader("Location: ...").
- 7Reject any request whose method is notPOST.
- 8Add a session counter to rate-limit repeated calls.
In this tutorial iam talking about PHP direct access, CSRF "Cross site request forgery" , Redirect Bug and DOS "Denial of service" attacks.