Rails PWA Integration#
Overview#
Sure's Progressive Web App endpoints are handled by PwaController, which serves two public assets: the web app manifest (/manifest) and the service worker (/service-worker). Because these assets must be accessible to unauthenticated browsers and browser behavior varies significantly in how it requests them, the controller applies two hardening measures: explicit format overrides and authentication bypass.
Routes#
Both endpoints are declared in config/routes.rb as simple GET routes with route-level defaults that set a fallback format:
GET /service-worker→pwa#service_worker(defaults: { format: :js })GET /manifest→pwa#manifest(defaults: { format: :json })
The route-level defaults alone proved insufficient (see Format Negotiation below).
Format Negotiation#
Rails infers the response format from the request's Accept header. PWA endpoints are hit by a wide variety of clients — mobile Safari, Yandex Browser, crawlers — that frequently send Accept: text/html instead of the expected application/javascript or application/manifest+json. This caused ActionView::MissingTemplate errors because Rails would look for pwa/manifest.html.* or pwa/service-worker.json.* templates that don't exist.
Three successive fixes hardened this behavior:
| Fix | PR | What changed |
|---|---|---|
Initial PwaController + route defaults | #1828 | Created dedicated controller; added defaults: { format: :json } to manifest route |
Explicit formats: [:json] in manifest render | #2508 | Route default alone was insufficient; render call now pins the format |
Explicit formats: [:js] in service worker render | #2629 | Same pattern applied to service worker after JSON Accept headers triggered the same error |
The current PwaController overrides format in the render call itself:
manifestaction:render "pwa/manifest", formats: [:json], content_type: "application/manifest+json"service_workeraction:render "pwa/service-worker", formats: [:js], content_type: "application/javascript"
This ensures Rails always resolves pwa/manifest.json.erb and pwa/service-worker.js respectively, regardless of what the Accept header says.
Authentication & CSRF#
PwaController calls skip_authentication at the class level, which skips the authenticate_user! and set_sentry_user before-actions . This makes both endpoints publicly accessible without a session.
CSRF (protect_from_forgery) is not explicitly configured for these endpoints. Because both routes are GET-only, Rails' default CSRF protection does not apply — CSRF tokens are only enforced on state-changing request methods (POST/PATCH/PUT/DELETE).
Key Files#
| File | Purpose |
|---|---|
app/controllers/pwa_controller.rb | Controller with format + content-type overrides |
config/routes.rb:810-812 | Route declarations with fallback defaults |
app/views/pwa/manifest.json.erb | Web app manifest template |
app/views/pwa/service-worker.js | Service worker implementation |