Skip to main content

React Advanced

Content Security Policy

What c15t adds to the page

A React app with c15t adds these to the page, and your policy must allow them:

WhatDirective
The <style> elements the banner, dialog and other stock components render with c15t's rulesstyle-src with a nonce
c15t/react/styles.css, only if you import it with styles: falsestyle-src 'self'
The <style id="c15t-theme"> element, if you render ConsentThemestyle-src with a nonce or its hash
<script> elements the script loader creates for scripts entries, both src and inlinescript-src with each vendor's host, or a nonce
With server rendering, the inline <script> before the banner's buttons that holds a tap made before hydrationscript-src with a nonce or its hash
Browser requests to your backend's /init and /subjectsconnect-src with the backend origin
Iframes inside ConsentGate, and the frames, images and requests your vendors loadframe-src, img-src, connect-src for each vendor

In a client-rendered app, c15t components set their inline styles through React. A policy does not restrict styles set that way, so they need no directive.

With hosted(), the browser calls GET {url}/init on every page load and POST {url}/subjects when the visitor accepts, rejects or saves. With manifest(), it calls POST {url}/subjects, and GET {url}/init only when the policy needs a location the browser does not know. Add the backend's origin to connect-src:

Content-Security-Policy (partial)
connect-src 'self' https://<your-backend-host>;

Use the origin of the backend URL from your Inth project or self-hosted backend. When the URL is a same-origin path such as /api/c15t behind your own proxy, 'self' covers it.

Under an IAB TCF policy the browser also loads the Global Vendor List from the URL the policy provides. Allow that host in connect-src if you use the IAB TCF add-on.

Allow vendor scripts

The script loader adds a <script> element for each scripts entry once its category is allowed. List each vendor's script host in script-src, and the hosts its script calls in connect-src and img-src. The vendor's own documentation lists them. Scripts that a vendor script adds itself, such as a tag manager's tags, need their own hosts too, or 'strict-dynamic' with a nonce.

A scripts entry with inline textContent instead of src needs a nonce or its hash in script-src.

Use a nonce

A nonce protects a page only when it changes on every response. A static host serves the same file to everyone, so use host allowlists there. Use a nonce when a server renders or rewrites your index.html per request.

Pass the nonce as options.nonce on ConsentProvider. The script loader stamps it on every <script> it creates. A nonce on a single scripts entry takes precedence for that element. The stock components put it on the <style> elements that carry c15t's rules.

With Vite, set html.cspNonce to a placeholder in vite.config.ts, and have your server replace it with a new nonce on every response. Vite then adds <meta property="csp-nonce" nonce="..."> to the page, and the browser keeps the value in the tag's nonce property. Read it where you create the provider, in the quickstart's src/consent.tsx:

src/consent.tsx (partial)
const nonce =
  document.querySelector<HTMLMetaElement>('meta[property="csp-nonce"]')
    ?.nonce || undefined;

<ConsentProvider options={{ mode, scripts, nonce }}>

If you render ConsentTheme, pass it the same value: <ConsentTheme theme={theme} nonce={nonce} />.

The provider reads options.nonce once, when it mounts. A server-rendered React app, such as one built with React Router framework mode, reads the nonce from its own request and passes it the same way.

Allow c15t's style elements

The banner, dialog and other stock components render c15t's rules as <style> elements. A style-src without 'unsafe-inline' must admit them. Either:

  • Set options.nonce on ConsentProvider, as in use a nonce. The elements then render next to each component with the nonce. React 19 would otherwise move them into <head> and drop the nonce.
  • Or set styles: false in the provider options and import c15t/react/styles.css from your bundle. style-src 'self' admits it.

Allow the theme style element

ConsentTheme renders your theme tokens into a <style id="c15t-theme"> element. Without a nonce, a style-src that lacks 'unsafe-inline' blocks it, and the banner falls back to the default theme. The element's content depends only on the theme, so a hash works:

  1. Load the page with the policy enforced.
  2. Copy the sha256-... value from the Refused to apply inline style console error.
  3. Add it to style-src and redeploy.

Recompute the hash when you change the theme. To avoid the hash, set the tokens in your own stylesheet instead, as customize shows.

Allow embeds and the dev server

Add the host of every iframe you gate to frame-src. A YouTube player inside ConsentGate needs https://www.youtube-nocookie.com, for example.

Vite's dev server injects CSS as <style> elements, and the DevTools panel adds a <style> element without a nonce. Test the policy against a production build.

Verify

Build the app, serve it with the policy enforced, not in report-only mode, and open DevTools. Test under a policy that asks for a choice, such as an EU opt-in policy.

  1. The banner renders with its theme colors, and the console shows no Refused to error that mentions a c15t element, your backend or a vendor.
  2. Before a choice, the Network tab shows no vendor requests.
  3. Click Accept All. The vendor scripts load, and their <script> elements have ids starting c15t-. With a nonce, read each element's nonce property in the console. Browsers hide the attribute from getAttribute.
  4. The save to /subjects succeeds. A connect-src violation here means the backend origin is missing.
  5. Reload. The banner stays closed, and the vendors load without a console error.