Security Tip: Safely Rendering JSON in Blade
[Tip#41] It's quite common to inject JSON into Blade templates - but is it safe?
Greetings my friends! We’re continuing our series of tips covering simple helpers with security benefits by looking at a simple helper function in Laravel for getting JSON safely into Blade templates, without needing to worry about XSS.
I’m excited to let you all know that I launched the next module in Practical Laravel Security today, covering Cross-Site Request Forgery (CSRF) attacks! You can read all the details over here, or if you’ve already signed up, you’ll find it here. I built 6 interactive challenges for this module, which take you through from a basic copy-pasted form, through to abusing a subdomain, and then stealing an unprotected CSRF token. 😎
Please consider becoming a paid subscriber to support Laravel Security in Depth.
You’ll receive weekly security tips and monthly In Depth articles, covering every aspect of building secure applications in Laravel.
👉 Security Audits: Want me to hack your app and help you improve your security? 🕵️
Looking to learn more?
⏩ Security Tip #26: Type Juggling
▶️ In Depth #9: Signed URLs
Safely Rendering JSON in Blade
It’s a pretty common approach to pass data between your backend and frontend through Blade by rendering a block of JSON into a JS variable.
Something like this:
<script>
var options = <?php echo json_encode($options); ?>;
</script>
However, is this safe?
Well, it depends... In earlier versions of PHP, it wasn’t safe. Now… maybe?
The issue is that `json_encode()`
isn’t designed for outputting a safe block of JSON within a script block inside HTML. It’s default encoding only handles a basic subset of special cases, and there is always the risk that a new bypass is discovered that will allow for breaking out of the JSON and injecting a custom script.
Some of my favourites from older versions of PHP include simply using a `</script>`
tag or special “quotes”
that PHP will ignore but Javascript will translate into standard quotes.
So what should you do instead?
Laravel provides a helper class, `Illuminate\Support\Js`
`json_encode()`
within HTML.Using it is trivial:
<script>
var options = {{ Js::from($options) }};
</script>
It’s super simple to use - in fact it’s even shorter than the previous code - and will ensure no one can sneak anything into your JSON.
To wrap up, let’s review the difference in output - which nicely shows the benefits in using the helper.
<script>
// json_encode();
var options = {"elves":"three","dwarves":"seven","men":"nine","dark lord":"one"};
// Js::from()
var options = JSON.parse('{\u0022elves\u0022:\u0022three\u0022,\u0022dwarves\u0022:\u0022seven\u0022,\u0022men\u0022:\u0022nine\u0022,\u0022dark lord\u0022:\u0022\\u003C\\\/script\\u003E\u0022}');
</script>
QUICK UPDATE: 2023-05-24
A recent addition to Laravel added a new `Js::encode()`
helper, which you can use to encode JSON without the addition of `JSON.parse(...)`
that `Js::from()`
adds.
> Js::encode(['elves' => 'three', 'dwarves' => 'seven']);
= "{"elves":"three","dwarves":"seven"}"
These two helper functions should remove the need to use `@js()`
and `@json()`
entirely.
Don’t forget, you can put these safely inside `{{ ... }}`
tags too!
I was very disappointed when none of my tests worked.
Why you did not mention the @json directive?