Embedding Apple Music looks simple right up until your CSP blocks it and leaves you staring at a blank iframe.
I’ve hit this a few times on locked-down sites: the page loads fine, your own scripts work, and then the Apple Music player silently fails because frame-src or child-src doesn’t allow Apple’s embed origin. If you’re running a reasonably strict policy, you need to account for the iframe itself and, depending on your setup, any assets or network requests your page makes around it.
Here’s how I’d approach CSP for Apple Music embeds on a real site.
The embed markup
A typical Apple Music embed looks like this:
<iframe
allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write"
frameborder="0"
height="450"
style="width:100%;max-width:660px;overflow:hidden;border-radius:10px;"
sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation"
src="https://embed.music.apple.com/us/album/1989-taylors-version/1708308989"
></iframe>
From a CSP perspective, the main thing is the src:
https://embed.music.apple.com
That means your policy needs to allow Apple Music in frame-src or child-src.
The minimum CSP change
If your site already has a CSP and you just want Apple Music embeds to render, start here:
Content-Security-Policy: default-src 'self'; frame-src 'self' https://embed.music.apple.com; object-src 'none'; base-uri 'self';
That’s the smallest useful change for the iframe itself.
If you’re using an older CSP setup or want broader compatibility, child-src can also be included:
Content-Security-Policy: default-src 'self'; child-src 'self' https://embed.music.apple.com; frame-src 'self' https://embed.music.apple.com; object-src 'none'; base-uri 'self';
frame-src is the directive I’d use first. child-src is mostly there if you’re maintaining older policies or want to be explicit.
If you want a refresher on directive behavior, https://csp-guide.com has solid CSP references.
Starting from a real production-style CSP
Here’s the real CSP header you provided from headertest.com:
content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-ZjgzMmYzNTItMDc2Yi00MzU0LTg2NmMtZjI3MmI1MDUzYzlk' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com; frame-src 'self' https://consentcdn.cookiebot.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
To support Apple Music embeds, I’d update frame-src like this:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-ZjgzMmYzNTItMDc2Yi00MzU0LTg2NmMtZjI3MmI1MDUzYzlk' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com;
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com;
frame-src 'self' https://consentcdn.cookiebot.com https://embed.music.apple.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
That one change is usually enough.
Why frame-src is the key directive
Apple Music embeds run inside an iframe hosted by Apple. Your page is not loading Apple’s scripts directly into your origin, which is good. It keeps the integration cleaner and safer.
So the browser checks whether your policy allows that frame to load:
frame-srccontrols iframe sources- if
frame-srcis absent, the browser falls back tochild-src - if both are absent, it falls back to
default-src
That fallback behavior trips people up all the time.
For example, if you had this:
Content-Security-Policy: default-src 'self'; object-src 'none';
then the Apple Music iframe would be blocked, because default-src 'self' would apply to frames too.
A strict but practical policy for an Apple Music page
If I were building a page whose only third-party feature was an Apple Music embed, I’d keep the CSP tight:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-r4nd0m123';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-src 'self' https://embed.music.apple.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests
A few opinions here:
- I would not add broad
https:allowances just because one embed needs a frame. - I would not add Apple to
script-srcunless I had hard evidence my own page was loading Apple-hosted JavaScript directly. - I would keep
object-src 'none'every time unless I had some bizarre legacy requirement.
Example: Express with Helmet
If you’re serving a Node app, Helmet makes this painless.
import express from "express";
import helmet from "helmet";
const app = express();
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-r4nd0m123'"],
styleSrc: ["'self'"],
imgSrc: ["'self'", "data:"],
fontSrc: ["'self'"],
connectSrc: ["'self'"],
frameSrc: ["'self'", "https://embed.music.apple.com"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
frameAncestors: ["'none'"],
upgradeInsecureRequests: [],
},
},
})
);
app.get("/", (req, res) => {
res.send(`
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Apple Music CSP Demo</title>
</head>
<body>
<h1>Album Embed</h1>
<iframe
allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write"
frameborder="0"
height="450"
style="width:100%;max-width:660px;overflow:hidden;border-radius:10px;"
sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation"
src="https://embed.music.apple.com/us/album/1989-taylors-version/1708308989">
</iframe>
</body>
</html>
`);
});
app.listen(3000);
If your app generates per-request nonces, wire that up properly instead of hardcoding one like I did here for readability.
Example: Nginx header
For a static site or reverse proxy, just set the header directly:
add_header Content-Security-Policy "
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-src 'self' https://embed.music.apple.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none'
" always;
If your page uses inline styles for the iframe, like Apple’s copied embed snippets often do, your own CSP might block that style attribute depending on browser behavior and policy shape. The clean fix is to move presentation into your stylesheet instead of loosening CSP.
For example, instead of this:
<iframe
style="width:100%;max-width:660px;overflow:hidden;border-radius:10px;"
src="https://embed.music.apple.com/us/album/1989-taylors-version/1708308989">
</iframe>
do this:
<iframe
class="apple-music-embed"
src="https://embed.music.apple.com/us/album/1989-taylors-version/1708308989">
</iframe>
.apple-music-embed {
width: 100%;
max-width: 660px;
height: 450px;
overflow: hidden;
border-radius: 10px;
border: 0;
}
That lets you keep:
style-src 'self'
instead of reaching for 'unsafe-inline'.
Common breakages
1. The iframe is blank
Check the console. You’ll usually see something like:
Refused to frame 'https://embed.music.apple.com/' because it violates the following Content Security Policy directive: "frame-src 'self'".
Fix: add https://embed.music.apple.com to frame-src.
2. You only set default-src
This is the classic mistake:
Content-Security-Policy: default-src 'self'
Looks strict. Also breaks the embed.
Fix: explicitly define frame-src.
3. You copied a broad policy and over-allowed everything
I see this a lot after debugging sessions:
Content-Security-Policy: default-src * data: blob: 'unsafe-inline' 'unsafe-eval'
Yes, the embed works. So would half the internet. That’s not a win.
Keep the allowance narrow:
frame-src 'self' https://embed.music.apple.com
4. You’re mixing CSP and iframe sandbox problems
If the frame loads but some interaction is broken, look at the iframe’s sandbox attribute too. CSP decides whether the browser may load the frame. sandbox decides what that frame can do once loaded.
Apple’s embed snippets usually include a sandbox configuration already. Don’t randomly tighten it unless you test the player behavior afterward.
Report-Only first if you’re cautious
If this is a production site and you don’t want to risk breaking other embedded content, ship the change in report-only mode first:
Content-Security-Policy-Report-Only: default-src 'self'; frame-src 'self' https://embed.music.apple.com; object-src 'none'; base-uri 'self';
Then watch browser reports and console output before enforcing it.
If you already have an enforced policy, mirror the same change in your report-only policy and compare results.
The policy I’d ship
For most developer-facing sites embedding Apple Music, I’d ship something like this:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic';
style-src 'self';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self';
frame-src 'self' https://embed.music.apple.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests
If you already have analytics, consent tooling, or other third-party widgets, merge Apple Music into the existing policy instead of widening unrelated directives.
That’s really the whole game here: Apple Music embeds are mostly a frame-src problem. Don’t turn it into a script-src or default-src mess unless your app truly needs that.