Skip to content

Add service worker for authenticated image loading#3966

Merged
lukemelia merged 8 commits intomainfrom
cs-10159-authenticated-image-display-via-service-worker-auth
Feb 10, 2026
Merged

Add service worker for authenticated image loading#3966
lukemelia merged 8 commits intomainfrom
cs-10159-authenticated-image-display-via-service-worker-auth

Conversation

@lukemelia
Copy link
Copy Markdown
Contributor

@lukemelia lukemelia commented Feb 9, 2026

Summary

  • Adds a service worker that intercepts cross-origin <img> and CSS background-image requests to realm servers and injects Authorization: Bearer <jwt> headers
  • No server-side changes needed — the existing CORS config (origin: * with Authorization in allowed headers) works as-is
  • No card template changes needed — existing <img src={{@model.url}}> patterns work transparently
  • Replaces a previous cookie-based approach that was incompatible with wildcard CORS origins

How it works

  1. An instance initializer registers the service worker at app boot
  2. Per-realm JWT tokens are synced to the SW via postMessage whenever they're created, refreshed, or removed
  3. The SW intercepts GET/HEAD requests matching known realm URL prefixes and adds the Authorization header
  4. The SW upgrades no-cors requests (used by <img> elements) to cors mode so the header isn't silently stripped

Key files

File Purpose
packages/host/public/auth-service-worker.js The service worker — token storage, fetch interception, auth injection
packages/host/app/utils/auth-service-worker-registration.ts Registration, token sync via postMessage, lifecycle management
packages/host/app/instance-initializers/register-auth-service-worker.ts Eager boot at app startup
packages/host/app/services/realm.ts Integration: sync on persist/restore, clear on logout
packages/host/tests/unit/auth-service-worker-test.ts Unit tests for token management and fetch interception logic
packages/experiments-realm/authenticated-image-tester.gts Test card for manual verification

Closes CS-10159

Test plan

  • Verified locally: authenticated realm images load via both <img> tag and CSS background-image
  • Verify service worker registers and activates on fresh browser (no prior SW)
  • Verify images load after page refresh (tokens synced from localStorage)
  • Verify logout clears SW tokens (images stop loading)
  • Verify non-realm URLs (external CDNs, etc.) are not affected
  • Run existing test suite to confirm no regressions

🤖 Generated with Claude Code

lukemelia and others added 4 commits February 9, 2026 17:00
<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>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 9, 2026

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>
@lukemelia lukemelia force-pushed the cs-10159-authenticated-image-display-via-service-worker-auth branch from 9c57be5 to 3bddb67 Compare February 9, 2026 22:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-cors image requests to cors).
  • 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.

Comment thread packages/host/public/auth-service-worker.js Outdated
Comment thread packages/host/app/utils/auth-service-worker-registration.ts Outdated
Comment thread packages/host/tests/unit/auth-service-worker-test.ts
Comment thread packages/experiments-realm/authenticated-image-tester.gts Outdated
Comment thread packages/host/app/services/realm.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 9, 2026

Host Test Results

    1 files  ±    0      1 suites  ±0   3h 26m 9s ⏱️ + 1h 37m 19s
1 962 tests +   13  1 945 ✅ +   13  17 💤 ± 0  0 ❌ ±0 
3 885 runs  +1 921  3 852 ✅ +1 905  33 💤 +16  0 ❌ ±0 

Results for commit a9c4d4d. ± Comparison against base commit 8def5b2.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown
Contributor

@backspace backspace left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

lukemelia and others added 3 commits February 9, 2026 19:53
- 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>
@lukemelia lukemelia merged commit a92e236 into main Feb 10, 2026
117 of 119 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants