Permissions Policy and the Permissions API

Permissions Policy is an HTTP header that lets you lock down which capabilities are available on your site — including blocking them from third-party iframes like ads.

April 4, 20264 min read7 / 7

By default, browser capabilities are available to the main HTML document loaded in a tab. But there is more to the permission story than just the user dialog. As a developer, you can also control which capabilities your site exposes -- and which ones you want to lock down.

Why Cross-Origin Iframes Are Restricted

Most APIs are not available inside cross-origin iframes by default. This was not always the case, and the reason it changed is straightforward: ads.

Ads on the web are almost always served as iframes from a third-party domain. At one point, those iframes could access the same browser APIs as the parent page -- geolocation, sensors, camera. An ad on a news site was reading the user's location without the user knowing. That kind of silent data access is exactly what the permission model is supposed to prevent.

The fix was to restrict cross-origin iframes from accessing sensitive APIs by default. If you own both the parent page and the iframe and want to share a capability, you have to explicitly opt in.

Permissions Policy: The HTTP Header

The way to opt in (or out) is an HTTP header called Permissions-Policy. Your server sends this header alongside the HTML response, and it tells the browser which capabilities are allowed for this page and for any iframes it contains.

Before this was called Permissions-Policy, it was called Feature Policy. You may still see the old name in older articles.

Enable a capability for your own origin and specific third-party domains:

HTTP
Permissions-Policy: geolocation=(self "https://maps.partner.com")

Disable a capability entirely -- no one can use it, not even your own scripts:

HTTP
Permissions-Policy: geolocation=()

An empty parentheses list means an empty allowlist. Nothing is permitted. If someone opens the browser console on your page and tries to call the Geolocation API, the browser will deny it -- because the server said so.

Combine multiple capabilities in one header:

HTTP
Permissions-Policy: geolocation=(self), camera=*, picture-in-picture=()

Here, geolocation is allowed for your own origin only, camera is allowed for everyone (the * wildcard), and picture-in-picture is disabled. You can also send multiple Permissions-Policy headers in the same response if you prefer to split them across lines.

For iframes specifically, the same values can go in an HTML attribute:

HTML
<iframe src="https://maps.partner.com/embed" allow="geolocation=(self)"> </iframe>

This gives you fine-grained control: one iframe can have geolocation, another cannot, regardless of what the header says at the page level.

Why You Might Want to Disable Capabilities You Don't Use

If your app is a banking dashboard and you have no reason to use Bluetooth or NFC, you might still want to explicitly disable them. Anyone who gets console access to your page -- through a browser extension, an injected script, or even a developer poking around -- can call those APIs using your origin's permissions. Turning them off server-side is a simple layer of defense.

The Permissions API

Alongside Permissions Policy, there is also a JavaScript API for checking the current permission state before calling a capability. The idea was to bring all permission checking into one place, since historically each capability API had its own way of knowing whether access was granted.

JavaScript
const result = await navigator.permissions.query({ name: 'geolocation' }); // result.state is one of: 'granted', 'prompt', or 'denied' if (result.state === 'granted') { // safe to call immediately } else if (result.state === 'prompt') { // calling the API will show a permission dialog } else { // denied — show a message, guide the user to browser settings }

If the device does not support the capability at all (no GPS hardware, for example), the query throws an exception rather than returning a state.

The practical limitation is that this API does not cover every capability. Each browser maintains its own sub-list of which permission names are supported in navigator.permissions.query. Geolocation is widely supported. Others are not. There is no reliable way to know ahead of time whether a specific name will work in a given browser. Because of this inconsistency, the Permissions API sees relatively little use in production -- most teams fall back to feature detection and try-catch patterns instead.

A Brief Note on History

Geolocation was the first browser capability API. It arrived with the original iPhone and Mobile Safari. At the time, Apple was promoting web apps as the primary way to build software for the iPhone -- the App Store did not exist yet. Geolocation was added to give those web apps something genuinely useful that desktop browsers did not have. Canvas for 2D drawing followed shortly after. Everything since has been building on that foundation.

Practice

0/6 done

Enjoyed this? Get more like it.

Deep dives on system design, React, web development, and personal finance — straight to your inbox. Free, always.