PHP cookbook · Beginner
Prevent XSS in PHP templates
Render user-generated content (names, comments, links) safely in HTML pages.
Approach
Escape every dynamic value at output time for its context. Use htmlspecialchars() with ENT_QUOTES | ENT_SUBSTITUTE and UTF-8 for HTML text and attributes, validate URL schemes for links, and use json_encode with hex flags when passing data to JavaScript. Prefer a templating engine with auto-escaping (Twig, Blade) and add a CSP header.
Solution
<?php
function e(?string $v): string { return htmlspecialchars($v ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
function safe_url(string $url): string
{
$scheme = strtolower((string) parse_url($url, PHP_URL_SCHEME));
return in_array($scheme, ['http', 'https', 'mailto'], true) ? $url : '#';
}
header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'");
?>
<article>
<h2><?= e($comment['author']) ?></h2>
<p><?= nl2br(e($comment['body'])) ?></p>
<a href="<?= e(safe_url($comment['website'])) ?>" rel="nofollow noopener">Website</a>
</article>
<script>
window.__DATA__ = <?= json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_THROW_ON_ERROR) ?>;
</script>
Pitfalls
- Escaping before storing (double-escaping and wrong context later).
javascript:URLs in href attributes.- Bypassing auto-escaping with
|rawor{!! !!}for user content.
Variations
- Allow limited formatting with Markdown + an HTML sanitizer (Symfony HtmlSanitizer, HTML Purifier).
Further reading: OWASP XSS Prevention Cheat Sheet
Reference: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
More PHP recipes
- Add CSRF protection to PHP forms
- Add health check endpoints to a PHP app
- Add structured logging with request IDs in PHP
- Build a secure password reset flow in PHP
- Cache expensive queries with Redis in PHP
- Call external APIs with timeouts and retries in PHP
- Handle file uploads securely in PHP
- Implement rate limiting in a PHP API