Repository:
https://github.com/mmycin/Frontend-Proxy-Bypass
Purpose: Educational – Show how protected backend APIs can be indirectly accessed through trusted frontend clients, even with strong authentication.
Warning: For learning and security testing only. Do not deploy in production.
This project demonstrates a critical architectural flaw in web applications:
Even if your backend API requires an API key, if a public-facing frontend includes that key and forwards user input — attackers can bypass authentication entirely.
It implements a multi-tier attack chain:
[Attacker]
→ [Public PHP Form]
→ [Protected Express API]
← Only accepts Bearer: scraperapi
← [Response embedded in HTML]
← [Scraper parses HTML → gets JSON]
No API key needed by the attacker.
Frontend-Proxy-Bypass/
│
├── backend/ # Node.js + Express (Protected API)
│ ├── src/
│ │ ├── index.ts # Main server
│ │ └── middleware/
│ │ └── auth-header.ts # API Key validation
│ └── package.json
│
├── frontend/ # Public PHP Form (The Proxy)
│ ├── index.php # Form + cURL to backend
│ └── .env # Contains API key (DANGEROUS)
│
├── scraper/ # Attacker's Tool (FastAPI)
│ └── main.py # Submits to PHP → extracts backend response
│
└── README.md # This file
const app = express();
app.use(express.json());
app.get("/public", ...) // Open to all
app.post("/protected", authorizeWithApiKey, ...) // Requires Bearer token- Runs on
http://localhost:3000 - Only
/protectedis secured - Uses
API_KEYfrom environment
const VALID_API_KEYS = process.env.API_KEY || "";Note: Currently treats API_KEY=key1,key2 as a single string, not array.
In this PoC: API_KEY=scraperapi → works.
if (apiKey === VALID_API_KEYS) next();
else res.status(403).json({ message: "Forbidden" });Security: Strong if the key stays secret.
$apiToken = $env['API_TOKEN']; // = scraperapi
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $apiToken"
]);- Public form (Name, Email, Message)
- On submit → PHP:
- Sanitizes input (
htmlspecialchars) - JSON-encodes payload
- cURL POST to
http://localhost:3000/protected - Includes API key in header
- Displays raw backend response in
<pre>
- Sanitizes input (
API_TOKEN=scraperapi
API_URL=http://localhost:3000/protected/Critical Flaw: The API key is stored on a public-facing server
Anyone who can submit the form → triggers authenticated backend calls
@app.post("/protected")
def submit_form(data: FormData):
response = requests.post(PHP_FORM_URL, data=data.dict())
soup = BeautifulSoup(response.text, "html.parser")
pre = soup.select_one("div.output pre")
json_str = html.unescape(pre.text)
return json.loads(json_str)- Runs FastAPI server (
localhost:8000) - Accepts JSON input
- Submits form data to PHP page
- Parses HTML to extract
<pre>containing backend JSON - Returns clean JSON response
Attacker never touches the API key
Uses frontend as proxy
sequenceDiagram
participant A as Attacker (scraper)
participant F as PHP Form (frontend)
participant B as Express API (backend)
A->>F: POST {name, email, message}
F->>B: cURL POST + Bearer: scraperapi
B-->>F: JSON { success, received }
F-->>A: HTML with <pre>{...}</pre>
A->>A: Parse HTML → Extract JSON
Note right of A: Full backend access<br>without API key!
| Tool | Version |
|---|---|
| Node.js | >= 14 |
| PHP | >= 7.4 (with cURL) |
| Python | >= 3.8 |
| npm/yarn | latest |
cd backend
npm install
API_KEY=scraperapi npm start
# → http://localhost:3000cd ../frontend
php -S localhost:8080
# → Open http://localhost:8080 in browsercd ../scraper
pip install fastapi uvicorn requests beautifulsoup4 lxml
uvicorn main:app --reload --port 8000
# → http://localhost:8000- Go to
http://localhost:8080 - Fill form → Submit
- See backend JSON in blue box
curl -X POST http://localhost:8000/protected \
-H "Content-Type: application/json" \
-d '{
"name": "Malicious User",
"email": "hack@evil.com",
"message": "I bypassed your auth!"
}'Expected Output:
{
"success": true,
"data": {
"message": "Success! You accessed the protected route...",
"received": {
"name": "Malicious User",
"email": "hack@evil.com",
"message": "I bypassed your auth!"
}
}
}No API key used. Full access granted.
| Risk | Severity | Description |
|---|---|---|
| API Key Exposure | Critical | Key stored in .env on public server |
| Frontend Proxy | Critical | Public form acts as authenticated client |
| No CSRF Protection | High | Form can be submitted from anywhere |
| HTML Response Parsing | Medium | Scraper relies on fragile DOM structure |
| No Rate Limiting | Medium | Can be used for spam/DoS |
| Issue | Secure Alternative |
|---|---|
| API key in frontend | Never store secrets in client-accessible code |
| Frontend calls backend directly | Use backend-to-backend auth (mTLS, internal JWT) |
| No CSRF | Add CSRF tokens or SameSite cookies |
| HTML scraping | Return JSON API, not embed in HTML |
| Static API key | Use short-lived tokens, OAuth2, or client certificates |
| No logging | Log all form submissions with IP, user-agent |
This PoC teaches:
-
Authentication ≠ Authorization
- Just requiring a key isn't enough if it's leaked.
-
Frontend should never be trusted
- Any public endpoint with secrets is a proxy.
-
Defense in Depth
- Protect at multiple layers: network, app, data.
-
Why SSRF matters
- Even without direct server access, forms can be abused.
- Contact forms sending emails via backend (if backend trusts form)
- Admin dashboards with embedded API keys
- Mobile apps hardcoding backend tokens
MIT License
Free to use, modify, and distribute for educational and security testing purposes.
This is a security demonstration.
Never deploy this pattern in production.
Treat all frontend code as public.
"Your API is only as secure as its weakest client."
— Frontend Proxy Bypass PoC