Skip to main content

Overview

A Content Security Policy (CSP) is a security standard that helps prevent cross-site scripting (XSS), clickjacking, and other code injection attacks. It works by telling the browser exactly which sources of content are trusted. Dynamic’s SDK injects inline <style> elements to render its authentication widgets, including styles within Shadow DOM containers. By default, a strict CSP that disallows inline styles (style-src 'self') will block these styles and break the widget’s appearance. The cspNonce setting solves this by letting you pass a server-generated nonce to the SDK. Dynamic applies that nonce to every inline <style> and <link> tag it creates, so they pass your CSP without requiring 'unsafe-inline'.

How it works

  1. Your server generates a unique, unpredictable nonce for each page request
  2. Your server includes the nonce in both the CSP header and passes it to your application
  3. You pass the nonce to DynamicContextProvider via settings.cspNonce
  4. The SDK automatically applies the nonce to all inline styles it injects, including:
    • Shadow DOM reset and widget styles
    • Theme styles (<style> and <link> tags in <head>)
    • CSS overrides
    • Legacy browser compatibility styles
The browser then allows <style> tags with the matching nonce and blocks everything else, keeping your strict CSP intact.

Setup

1

Generate a nonce on your server

Create a unique, unpredictable nonce for every page request. Use a cryptographically secure random value — never hardcode or reuse a nonce.
import crypto from 'crypto';

app.use((req, res, next) => {
  // Generate a unique nonce per request
  res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
  next();
});
middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64');

  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}';
    style-src 'self' 'nonce-${nonce}';
    style-src-elem 'self' 'nonce-${nonce}' https:;
    img-src 'self' data: blob:;
    font-src 'self' data:;
    connect-src 'self' https:;
    frame-src 'self' https://app.dynamicauth.com;
  `.replace(/\n/g, '');

  const response = NextResponse.next();

  response.headers.set('Content-Security-Policy', cspHeader);
  response.headers.set('x-nonce', nonce);

  return response;
}
Then read the nonce in your layout:
app/layout.tsx
import { headers } from 'next/headers';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const nonce = (await headers()).get('x-nonce') ?? '';

  return (
    <html lang="en">
      <body>
        <DynamicContextProvider
          settings={{
            environmentId: 'YOUR_ENVIRONMENT_ID',
            cspNonce: nonce,
          }}
        >
          {children}
        </DynamicContextProvider>
      </body>
    </html>
  );
}
2

Set the CSP header

Include the nonce in your style-src or style-src-elem directive. If you use embedded wallets, also include frame-src for the iframe connection.
app.use((req, res, next) => {
  const nonce = res.locals.cspNonce;

  res.setHeader(
    'Content-Security-Policy',
    [
      "default-src 'self'",
      `style-src 'self' 'nonce-${nonce}'`,
      `style-src-elem 'self' 'nonce-${nonce}' https:`,
      `script-src 'self' 'nonce-${nonce}'`,
      "img-src 'self' data: blob:",
      "font-src 'self' data:",
      "connect-src 'self' https:",
      "frame-src 'self' https://app.dynamicauth.com",
    ].join('; ')
  );

  next();
});
For static sites where you cannot set HTTP headers, you can use a meta tag. Note that this is less secure than a server-set header because the nonce is visible in the HTML source.
<meta
  http-equiv="Content-Security-Policy"
  content="style-src 'self' 'nonce-YOUR_NONCE'; style-src-elem 'self' 'nonce-YOUR_NONCE' https:; frame-src 'self' https://app.dynamicauth.com;"
/>
# Generate nonce via a module like nginx-nonce or set_by_lua
set $csp_nonce $request_id;

add_header Content-Security-Policy
  "default-src 'self'; style-src 'self' 'nonce-$csp_nonce'; style-src-elem 'self' 'nonce-$csp_nonce' https:; frame-src 'self' https://app.dynamicauth.com;";
Vercel does not support dynamic nonces in vercel.json headers. Use Next.js middleware (shown above) or a Vercel Edge Function to generate nonces per request.
If you’re already setting CSP headers, append the nonce to your existing style-src and style-src-elem directives rather than replacing them.
3

Pass the nonce to DynamicContextProvider

Pass the nonce string as cspNonce in your provider settings. The SDK will apply it to every inline <style> and <link> element it creates.
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';

function App() {
  return (
    <DynamicContextProvider
      settings={{
        environmentId: 'YOUR_ENVIRONMENT_ID',
        cspNonce: nonce, // The nonce from your server
      }}
    >
      <YourApp />
    </DynamicContextProvider>
  );
}

Full example: Express + React

Here is a complete example showing nonce generation, CSP header configuration, and SDK integration. Server (Express):
server.js
import express from 'express';
import crypto from 'crypto';
import path from 'path';

const app = express();

app.use((req, res, next) => {
  const nonce = crypto.randomBytes(16).toString('base64');
  res.locals.cspNonce = nonce;

  res.setHeader(
    'Content-Security-Policy',
    [
      "default-src 'self'",
      `style-src 'self' 'nonce-${nonce}'`,
      `style-src-elem 'self' 'nonce-${nonce}' https:`,
      `script-src 'self' 'nonce-${nonce}'`,
      "img-src 'self' data: blob:",
      "font-src 'self' data:",
      "connect-src 'self' https:",
      "frame-src 'self' https://app.dynamicauth.com",
    ].join('; ')
  );

  next();
});

app.get('/', (req, res) => {
  const nonce = res.locals.cspNonce;

  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
      </head>
      <body>
        <div id="root"></div>
        <script nonce="${nonce}">
          window.__CSP_NONCE__ = "${nonce}";
        </script>
        <script nonce="${nonce}" src="/bundle.js"></script>
      </body>
    </html>
  `);
});

app.listen(3000);
Client (React):
App.tsx
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum';

const cspNonce = (window as any).__CSP_NONCE__;

export const App = () => (
  <DynamicContextProvider
    settings={{
      environmentId: 'YOUR_ENVIRONMENT_ID',
      walletConnectors: [EthereumWalletConnectors],
      cspNonce,
    }}
  >
    <YourApp />
  </DynamicContextProvider>
);

CSP directives reference

Here is a recommended baseline CSP for applications using Dynamic:
DirectiveRecommended ValuePurpose
default-src'self'Fallback for all resource types
style-src'self' 'nonce-{nonce}'Allows nonced inline styles and same-origin stylesheets
style-src-elem'self' 'nonce-{nonce}' https:Allows <style> and <link> elements with the nonce, plus external HTTPS stylesheets
script-src'self' 'nonce-{nonce}'Allows nonced inline scripts and same-origin scripts
img-src'self' data: blob:Allows images from same origin, data URIs, and blob URIs
font-src'self' data:Allows fonts from same origin and data URIs
connect-src'self' https:Allows API calls to same origin and HTTPS endpoints
frame-src'self' https://app.dynamicauth.comRequired for embedded wallets (iframe connection)
The style-src-elem directive (CSP Level 3) provides granular control over <style> elements separately from inline style="" attributes. If your browser support requires it, use style-src instead — it covers both. When a nonce is present in style-src, browsers that support CSP Level 2+ will ignore 'unsafe-inline' for <style> elements, requiring the nonce.

Approaches to avoid

ApproachWhy to avoid
style-src 'unsafe-inline'Allows all inline styles, defeating the purpose of CSP for style injection attacks
style-src 'unsafe-hashes'Only works for static inline styles — breaks when Dynamic injects styles with dynamic content
No CSP at allLeaves your application vulnerable to XSS-based style injection

Verifying your setup

You can verify that the nonce is correctly applied by inspecting Dynamic’s style elements in your browser’s DevTools:
  1. Open DevTools > Elements
  2. Find a .dynamic-shadow-dom element and expand its shadow root
  3. Inspect the <style> tags inside — each should have a nonce attribute matching your CSP
You can also check the Console for CSP violation errors. If the nonce is correctly configured, you should see no style-src-elem violations from Dynamic’s styles.

Troubleshooting

Make sure the nonce value passed to cspNonce exactly matches the nonce in your CSP header. The CSP header format is 'nonce-{value}' while the cspNonce prop expects just the value (without the nonce- prefix or quotes).
Other libraries in your application (wallet connectors, toast libraries, etc.) may also inject inline styles without nonce support. These violations are not caused by Dynamic. Check if those libraries offer their own nonce or CSP configuration options.
Dev servers like Vite inject CSS via inline <style> tags for hot module replacement (HMR). These won’t have your nonce and will trigger CSP violations. This is expected in development — in production builds, CSS is bundled into external files that load via <link> tags (allowed by 'self').
If you’re using embedded wallets, make sure your CSP includes frame-src 'self' https://app.dynamicauth.com. See the embedded wallet setup guide for details.