Add service worker for authenticated image loading#3966
Merged
Conversation
<img> elements and CSS background-image cannot send Authorization headers, so images hosted in authenticated realms fail to load. Instead of using cookies (which are incompatible with wildcard CORS origins), this adds a service worker that intercepts resource requests to realm servers and injects the JWT Authorization header transparently. - auth-service-worker.js: intercepts GET/HEAD requests matching known realm URL prefixes and adds Bearer token headers - auth-service-worker-registration.ts: manages SW lifecycle and syncs realm tokens from the main thread via postMessage - realm.ts: registers the SW on startup, syncs tokens on persist/restore, and clears tokens on logout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Example card for testing that the auth service worker correctly injects Authorization headers for realm-hosted images. Renders the same image URL via both <img> tag and CSS background-image, with instructions for verifying in DevTools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The controllerchange listener was added after await register(), which could miss the event if the SW activated quickly. Now the listener is added before registration starts, and tokens are also synced immediately after registration if a controller already exists (returning visitors). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The RealmService is lazily instantiated by Ember, so its constructor ran after cards already rendered and fired image requests. Moving the SW registration and token sync to an instance initializer ensures it happens at app boot, before any card rendering triggers authenticated image loads. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6 tasks
Contributor
Preview deployments |
Cross-origin <img> and CSS background-image requests use mode: 'no-cors' by default, which causes the browser to silently strip non-safelisted headers like Authorization. The SW now creates a new cors-mode request instead of cloning the original, so the Authorization header is actually sent to the realm server. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9c57be5 to
3bddb67
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces an auth-aware service worker to enable authenticated loading of realm-hosted images (and CSS background images) by injecting Authorization: Bearer <jwt> headers into matching cross-origin resource requests, without requiring template or server-side changes.
Changes:
- Added a service worker that intercepts GET/HEAD requests to known realm URL prefixes and injects Authorization headers (including upgrading
no-corsimage requests tocors). - Added Ember-side registration + token synchronization to the service worker (sync on boot, persist/restore, and clear on logout).
- Added unit tests for message-driven token management + request interception logic, plus an experiments card for manual verification.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/host/public/auth-service-worker.js | Implements SW token cache, fetch interception, and Authorization header injection. |
| packages/host/app/utils/auth-service-worker-registration.ts | Registers SW at boot and syncs/clears tokens via postMessage. |
| packages/host/app/instance-initializers/register-auth-service-worker.ts | Ensures SW registration happens early in app startup. |
| packages/host/app/services/realm.ts | Hooks token persist/restore/logout into SW synchronization. |
| packages/host/tests/unit/auth-service-worker-test.ts | Adds unit tests for token message handling and interception behavior. |
| packages/experiments-realm/authenticated-image-tester.gts | Adds a manual test card for <img> and CSS background-image verification. |
| packages/experiments-realm/AuthenticatedImageTester/1.json | Seed data for the experiments test card. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Contributor
backspace
approved these changes
Feb 10, 2026
Contributor
backspace
left a comment
There was a problem hiding this comment.
I tried locally with the same test realm I set up for the cookie version, success! Confirmed that the image won’t show anymore after logging out.
- Construct intercepted request from original Request object to preserve cache, referrer, integrity and other properties (was using request.url) - Import shared SessionLocalStorageKey constant instead of hardcoding - Update test processFetch to mirror SW's no-cors→cors mode upgrade and add dedicated test for mode upgrade behavior - Add URL sanitization to experiments image tester card to prevent CSS injection via url() context breakout - Sync SW token removal in RealmResource.logout() so reauthenticate() doesn't leave stale tokens in the service worker Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cross-origin <img> requests default to credentials: 'include'. When constructing the new Request from the original, this gets inherited. With mode: 'cors' + credentials: 'include', the browser requires Access-Control-Allow-Origin to be a specific origin, but the realm server uses '*'. Explicitly set credentials: 'same-origin' to avoid this incompatibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The regex /["'();]/ confused glint's parser due to quotes inside the regex literal. Use new RegExp() constructor instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
<img>and CSSbackground-imagerequests to realm servers and injectsAuthorization: Bearer <jwt>headersorigin: *withAuthorizationin allowed headers) works as-is<img src={{@model.url}}>patterns work transparentlyHow it works
postMessagewhenever they're created, refreshed, or removedAuthorizationheaderno-corsrequests (used by<img>elements) tocorsmode so the header isn't silently strippedKey files
packages/host/public/auth-service-worker.jspackages/host/app/utils/auth-service-worker-registration.tspackages/host/app/instance-initializers/register-auth-service-worker.tspackages/host/app/services/realm.tspackages/host/tests/unit/auth-service-worker-test.tspackages/experiments-realm/authenticated-image-tester.gtsCloses CS-10159
Test plan
<img>tag and CSSbackground-image🤖 Generated with Claude Code