Security Tip: Safely Rendering JSON in Blade

[Tip#41] It's quite common to inject JSON into Blade templates - but is it safe?

Security Tip: Safely Rendering JSON in Blade

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:

    var options = <?php echo json_encode($options); ?>;

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.1

So what should you do instead?

Laravel provides a helper class, `Illuminate\Support\Js`2, which includes the extra encoding flags you want when using `json_encode()` within HTML.

Using it is trivial:

    var options = {{ Js::from($options) }};

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.

// 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}');

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!

  1. I was very disappointed when none of my tests worked.