# Secure a PWA

> How to serve a PWA in a secure context, constrain executable resources with Content Security Policy, and keep service worker scope intentional.

A PWA uses a secure context for powerful APIs, a Content Security Policy (CSP) for resource loading, and an intentional service worker scope for request control. Configure and check these boundaries during deployment.

Use `window.isSecureContext` to check the active context and test for `serviceWorker` in `navigator` before registration. CSP violations can be observed in the document with `ReportingObserver` or sent to an endpoint configured with reporting headers and directives ([Secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts), MDN; [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy), MDN).

## Where it is supported

MDN describes `ServiceWorkerContainer.register()` as widely available across browsers since April 2018 ([ServiceWorkerContainer.register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register), MDN). MDN marks the CSP response header as widely available, although support for individual directives varies ([Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy), MDN).

## Serve the application in a secure context

MDN defines potentially trustworthy origins to include HTTPS and local development origins such as `http://localhost`. Features restricted to secure contexts are unavailable when the active document is not trustworthy. Redirect production HTTP traffic to HTTPS, and check the runtime property before starting a feature that requires a secure context.

## Detect the deployment boundary

The fallback keeps navigation and online content available while skipping service worker registration.

```js
const status = document.createElement('output');
status.id = 'offline-status';
status.setAttribute('aria-live', 'polite');
document.body.append(status);

if (!window.isSecureContext) {
  status.textContent = 'Offline installation is unavailable on this connection.';
} else if (!('serviceWorker' in navigator)) {
  status.textContent = 'This browser runs the online experience.';
} else {
  navigator.serviceWorker.register('/sw.js', { scope: '/' })
    .then(() => { status.textContent = 'Service worker registration was created or updated.'; })
    .catch(() => { status.textContent = 'The online experience remains available.'; });
}
```

The explicit branches make a failed security or capability check visible without blocking the page.

:::caution
An HTTPS iframe inside a top-level HTTP document is not a secure context. Check `window.isSecureContext` inside the frame that uses the restricted API. Service worker registration also requires the worker script to have a valid JavaScript media type; a scope broader than the script's directory requires a matching `Service-Worker-Allowed` response header ([Secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts), MDN; [ServiceWorkerContainer.register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register), MDN).
:::

## Deliver CSP as a response header

The `Content-Security-Policy` response header lets a site restrict resources such as scripts, styles, images, frames, and connections. MDN's report-only example uses `Content-Security-Policy-Report-Only` during testing to report violations without blocking execution.

```http
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
```

This example limits default and script loads to the same origin, blocks plugins, restricts `<base>` URLs, and prevents embedding. Adapt the policy to resources the application actually loads.

:::security
Inline script is blocked by the example because `script-src` contains neither `'unsafe-inline'` nor a matching nonce or hash. Move startup code to a same-origin file, or issue a fresh nonce in the response and apply it to the allowed `<script>` elements, as described by MDN's CSP reference.
:::

## Limit service worker scope

The default registration scope is the directory containing the service worker script. To request a broader scope than that directory, the service worker script response must supply a `Service-Worker-Allowed` header that permits it ([ServiceWorkerContainer.register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register), MDN; [Service worker script response](https://w3c.github.io/ServiceWorker/#service-worker-script-response), W3C).

Place `/app/sw.js` under `/app/` and register it with `{ scope: '/app/' }` when it should control only that path. Avoid overlapping registrations: MDN warns that overlapping scopes are generally safer to avoid because only one registration controls a document.

## Verify the deployed boundaries

Inspect the final response, not only source files. Confirm that the page is a secure context, the CSP header is present on the document response, and `registration.scope` matches the intended URL boundary. Observe CSP violations with `ReportingObserver`, or send reports to an endpoint configured by the policy's reporting directives ([Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy), MDN).

## See also

- [Service worker lifecycle](/reference/service-worker/lifecycle/)
- [Manifest scope](/reference/manifest/scope/)
- [Offline guide](/guides/offline/)
- [Secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts) (MDN)
- [Service Workers specification](https://w3c.github.io/ServiceWorker/) (w3.org)