🚀AdvancedSecurity

Permissions Policy

Control which browser features (camera, geolocation, autoplay) are available to your page and embedded iframes using Permissions Policy.

Overview

Permissions Policy (formerly Feature Policy) is an HTTP header and HTML attribute that allows a page to control which powerful browser features are available both to the page itself and to any embedded iframes. It is a defense-in-depth security mechanism: even if an attacker injects a malicious iframe, Permissions Policy can prevent it from accessing the camera, microphone, geolocation, or other sensitive APIs. The policy is set in two places: the HTTP `Permissions-Policy` response header (controls the page and all its iframes) or the `allow` attribute on an `<iframe>` element (controls just that iframe). Policies that restrict the page to only what it needs reduce the attack surface and prevent embedded third-party content from silently using APIs the page never intended to expose. Common controlled features include: camera, microphone, geolocation, autoplay, fullscreen, payment, usb, accelerometer, and many more.

Key Points

  • •HTTP header: `Permissions-Policy: camera=(), microphone=()` disables for all origins
  • •`feature=*` allows for any origin; `feature=self` allows only same-origin; `feature=()` blocks everywhere
  • •Iframe attribute: `allow="camera; microphone; fullscreen"` grants listed features to the iframe
  • •Iframe `allow="camera 'src'"` grants only the iframe's src origin
  • •The `<meta http-equiv="Permissions-Policy">` approach is NOT supported use HTTP headers or iframe allow
  • •Permissions API (`navigator.permissions.query`) lets JS check if a feature is granted/denied before requesting it
  • •Violations are observable via `Reporting API` with `report-to` directive

Examples

HTTP Permissions-Policy header configuration

<!-- These go in your server configuration, not HTML -->
<!-- Example: Next.js next.config.ts headers -->

// next.config.ts
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Permissions-Policy',
            // camera: nobody gets it
            // geolocation: only this origin
            // fullscreen: anyone (CDN iframes, etc.)
            // autoplay: blocked
            value: 'camera=(), microphone=(), geolocation=(self), fullscreen=*, autoplay=()'
          }
        ]
      }
    ];
  }
};

The HTTP header applies to the page AND all its embedded iframes it is the most powerful control.

Controlling iframe permissions with the allow attribute

<!-- ✅ Embed a video player allow fullscreen and autoplay only -->
<iframe
  src="https://player.vimeo.com/video/12345"
  allow="autoplay; fullscreen; picture-in-picture"
  allowfullscreen
  width="640"
  height="360"
  title="Product demo video"
></iframe>

<!-- ✅ Embed a map allow geolocation for this iframe's origin only -->
<iframe
  src="https://maps.example.com/embed"
  allow="geolocation 'src'"
  title="Store location map"
></iframe>

<!-- ✅ Embed a payment widget allow payment API -->
<iframe
  src="https://checkout.stripe.com/payment"
  allow="payment"
  title="Secure payment"
></iframe>

<!-- ❌ DANGEROUS: no allow restrictions at all -->
<!-- <iframe src="https://ads.example.com/banner"></iframe> -->

Only grant features that the iframe actually needs. An ad iframe should not have camera or microphone.

Querying permission status from JavaScript

// Check if camera permission is granted before requesting it
async function checkCameraPermission() {
  try {
    const status = await navigator.permissions.query({ name: 'camera' });

    switch (status.state) {
      case 'granted':
        startCamera();
        break;
      case 'prompt':
        // Will ask user show UI explaining why we need camera
        showCameraExplanation();
        break;
      case 'denied':
        // Blocked by user or Permissions-Policy
        showCameraBlockedMessage();
        break;
    }

    // Listen for changes (user grants/revokes in browser settings)
    status.addEventListener('change', () => {
      console.log('Camera permission changed to:', status.state);
    });
  } catch (err) {
    console.error('Permission query failed:', err);
  }
}

Common Mistakes

  • Trying to set Permissions-Policy via a <meta> tag this is not supported. Only HTTP response headers and iframe allow attributes work
  • Using `allowfullscreen` attribute WITHOUT corresponding `allow="fullscreen"` modern browsers require both for full compatibility
  • Granting all features with `allow="*"` in iframes this is equivalent to no policy; grant only what the iframe needs

Note

Use Chrome DevTools > Application > Permissions Policy to see which features are allowed or blocked on the current page and each frame. This makes debugging policy conflicts much easier.

Exercises

Write HTML to solve each exercise. Use Preview to see it render in a live iframe, then mark as done.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What does `geolocation=()` mean in a Permissions-Policy header?