Written by Giles Bennett
Our last two blogs looked firstly at implementing a Content Security Policy in Laravel, and then at implementing simple caching through APC. If you use nonces, though, rather than allowing scripts via hashes, you will find that you run into a bit of a problem.
The problem is that the cached response will return a cached nonce. By their very definition, nonces should only be used once. If the nonce in the script or style tag matches the nonce in the response header, it's allowed. But when the cached response returned the cached nonce, this will not match the nonce in the response header.
So...we obviously want the great speed benefits that caching will bring. But we also want the great benefits that a Content Security Policy will bring. How do we go about getting the one working with the other? The answer lies in a piece of middleware that will intercept the cached response and change any nonces in it to match the response header.
If you followed our guide on implementing a Content Security Policy then your app\Http\Middleware\Kernel.php will already look something like this :
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Spatie\Csp\AddCspHeaders::class,
],
Here you can see that the Content Security Policy headers are being added at the end of the web middleware group. We will want to add a new Middleware class that we'll call UpdateNonces, like so :
protected $middlewareGroups = [
'web' => [
UpdateNonces::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Spatie\Csp\AddCspHeaders::class,
],
Then we can create that class within our app\Http\Middleware folder :
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class UpdateNonces
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
return $response;
}
}
In handling the request, we're going to need to do the following, in this order :
1. Generate a new none.
2. Retrieve the CSP headers from the response.
3. Use Regex to find any reference in the existing headers to the existing nonce and replace it with the new nonce.
4. Set the response CSP headers with the updated headers.
5. Retrieve the content from the response.
6. Use Regex to find any reference in the existing content and replace it with the new nonce.
7. Set the content of the reesponse with the updated content.
What we end up with, therefore, looks like this :
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$newnonce = csp_nonce();
$existingheaders = $response->headers->get('Content-Security-Policy');
$newheaders = preg_replace('/nonce-.+?(?=\')/', 'nonce-'. $newnonce, $existingheaders);
$response->headers->set('Content-Security-Policy', $newheaders);
$existingcontent = $response->getContent();
$newcontent = preg_replace('/(?<=nonce="Gbku2UEbV6BbXf90k4uClCkZB6mOLtvN")/', $newnonce, $existingcontent);
$response->setContent($newcontent);
return $response;
}
None of these steps are particularly complicated, and a few of them can easily be merged, but for the sake of clarity I've left them laid out methodically.
Once that has been implemented, your caching and CSP will now work quite happily together.