ข้ามไปยังเนื้อหา

HTTPS และ Secure Context

Service worker และ API อื่นๆ ของ PWA ทำงานเฉพาะในสิ่งที่ browser เรียกว่า secure context การทำความเข้าใจว่า secure context คืออะไร ทำไมจึงสำคัญ และจะตรวจสอบได้อย่างไร จะช่วยให้คุณหลีกเลี่ยงปัญหาที่น่าหงุดหงิดในระหว่างการพัฒนาและ deployment

Secure context คือ Window หรือ Worker ที่ origin ถูกส่งมาผ่าน secure transport Browser เปิด boolean ให้ตรวจสอบได้ตลอดเวลา:

console.log(window.isSecureContext); // true or false

Browser จะตั้ง window.isSecureContext เป็น true เมื่อ:

  • หน้าถูกโหลดผ่าน https:// พร้อม TLS certificate ที่ valid
  • หน้าถูกโหลดจาก http://localhost, http://127.0.0.1 หรือ http://[::1] — loopback address เหล่านี้ได้รับการยกเว้นโดย spec โดยตรง และ browser สมัยใหม่ทุกตัวถือว่าปลอดภัย

สำหรับ origin http:// อื่นใด window.isSecureContext จะเป็น false และการ register service worker จะ throw SecurityError หรือล้มเหลวแบบเงียบๆ

Service worker เป็น proxy ที่ทรงพลัง สามารถ intercept ทุก request จากแอปของคุณ แก้ไข response และ cache เนื้อหาได้ หากถูก serve ผ่าน HTTP ทั่วไป ผู้โจมตีที่อยู่บนเครือข่ายเดียวกัน (man-in-the-middle) สามารถ:

  • แทนที่ script ของ service worker ด้วยโค้ดที่เป็นอันตรายได้
  • Intercept หรือดัดแปลง response ที่ service worker cache ไว้
  • ดักจับข้อมูลส่วนตัวก่อนที่จะถูกส่ง

การกำหนดให้ต้อง HTTPS จะทำให้แน่ใจว่า service worker ที่ browser รัน เป็นของจริงและไม่ถูกดัดแปลง

// Always guard registration with a secure-context check
if ('serviceWorker' in navigator) {
if (!window.isSecureContext) {
console.warn('Not a secure context — service worker will not register.');
} else {
navigator.serviceWorker
.register('/sw.js')
.then((reg) => console.log('Registered, scope:', reg.scope))
.catch((err) => console.error('Registration failed:', err));
}
}

หน้าจะถือว่าปลอดภัยก็ต่อเมื่อทุก resource ที่โหลดมาจาก secure origin เท่านั้น การโหลด http:// subresource แม้เพียงตัวเดียวจากหน้า https:// จะสร้าง mixed content ซึ่ง browser จะบล็อกหรือลดระดับ

Mixed content มีผลกับ PWA ในสองทางที่สำคัญ:

  1. Active mixed content (script, service worker, iframe ผ่าน http://) ถูกบล็อกเสมอ sw.js ของคุณเองต้อง serve ผ่าน https:// เท่านั้น
  2. Passive mixed content (image, audio, video ผ่าน http://) จะขึ้น warning และในบาง config อาจบังคับให้ window.isSecureContext เป็น false จนทำให้ register SW ไม่ได้

วิธีแก้ตรงไปตรงมา: ตรวจสอบ URL ของ resource ทุกตัวในแอป ย้ายทั้งหมดไปที่ https:// และใช้ Content Security Policy header เพื่อบังคับต่อไป หากหน้า HTTPS ของคุณโหลด resource ผ่าน http:// การ register service worker อาจยังคงทำงานได้ แต่ resource เหล่านั้นจะถูกบล็อกหรือทำให้ lock indicator ใน address bar เสียหาย ตรวจสอบ DevTools Console และ Network tab เพื่อค้นหาคำเตือน mixed content

Scope ของ service worker กำหนดว่ามันควบคุมหน้าใดบ้าง ค่าเริ่มต้นคือ directory ที่ไฟล์ SW อยู่:

SW file pathScope เริ่มต้นควบคุม
/sw.js/ทั้ง origin
/app/sw.js/app/เฉพาะ /app/ และด้านล่าง
/shop/checkout/sw.js/shop/checkout/เฉพาะ /shop/checkout/ และด้านล่าง

คุณสามารถทำให้ scope แคบลงได้โดยส่ง option scope ให้ register() แต่จะขยายให้กว้างกว่า directory ของไฟล์ SW ไม่ได้ — เว้นแต่ server จะส่ง response header Service-Worker-Allowed บนไฟล์ SW พร้อม scope ที่ต้องการ:

Service-Worker-Allowed: /
// Widen the scope with the header above set on /app/sw.js
navigator.serviceWorker.register('/app/sw.js', { scope: '/' });

หากไม่มี header นั้น การพยายาม register scope ที่กว้างกว่า directory ของไฟล์ SW จะ throw SecurityError ทั้งหมดนี้ถูก enforce อย่างเคร่งครัดภายใต้ HTTPS — ไม่มี loophole ใดที่จะอนุญาตให้ SW ที่ไม่น่าเชื่อถือ intercept traffic ที่ไม่ได้ตั้งใจให้ SW ตัวนั้นเข้าถึง

ระหว่างพัฒนาคุณไม่จำเป็นต้องมี self-signed certificate หรือ tunnel ใดๆ browser สมัยใหม่ทุกตัวถือว่า localhost, 127.0.0.1 และ ::1 เป็น secure origin เพราะ traffic บน loopback ไม่เคยผ่านเครือข่ายจริง จึงไม่มีความเสี่ยงจาก man-in-the-middle คุณจึง register service worker, ใช้ Push API, เข้าถึงกล้อง และทดสอบ installability flow ทั้งหมดได้บน http://localhost เดฟเซิร์ฟเวอร์ธรรมดา

// Full secure-context diagnostic you can drop into DevTools
(function diagnose() {
const results = {
isSecureContext: window.isSecureContext,
protocol: location.protocol,
hostname: location.hostname,
serviceWorkerSupported: 'serviceWorker' in navigator,
};
console.table(results);
if (!results.isSecureContext) {
console.warn(
'Secure context required. Deploy over HTTPS or use localhost.'
);
}
})();
// Safe registration pattern — checks secure context first
async function registerServiceWorker() {
if (!window.isSecureContext) {
console.warn('Secure context required. Skipping service worker registration.');
return;
}
if (!('serviceWorker' in navigator)) {
console.warn('Service workers not supported in this browser.');
return;
}
try {
const reg = await navigator.serviceWorker.register('/sw.js');
console.log('SW registered. Scope:', reg.scope);
} catch (err) {
console.error('SW registration failed:', err);
}
}
registerServiceWorker();
ตัวเลือกBenefitCost
บังคับ HTTPS ทุก origin (รวม subdomain)service worker และ API สมัยใหม่ทำงานได้เต็มรูปแบบ ปลอดภัยจาก MITMต้องจัดการ TLS cert และ redirect ให้ครบทุก environment รวม staging
อนุญาต localhost แบบ HTTP สำหรับ devพัฒนาและทดสอบได้เร็วโดยไม่ต้องตั้ง cert localต้องระวังไม่ให้ config นี้หลุดไปที่ production โดยไม่ตั้งใจ
  • ลืมว่า mixed content (โหลด asset ผ่าน http:// บนหน้า https://) ทำให้ browser บล็อก request นั้นเงียบๆ
  • ตั้ง redirect จาก HTTP ไป HTTPS ที่ระดับ app แทนที่จะทำที่ edge/CDN ทำให้ยังมี window ที่ request แรกไม่ปลอดภัย
  • ไม่ต่ออายุ TLS certificate ทันเวลา ทำให้ service worker ที่เคยลงทะเบียนไว้ใช้งานไม่ได้ทันทีที่ cert หมดอายุ

Chrome เริ่มติด label “Not Secure” ให้ทุกหน้าที่เป็น HTTP ธรรมดาตั้งแต่ปี 2018 ผลักดันให้อุตสาหกรรมส่วนใหญ่ย้ายไปใช้ TLS (Chromium Blog, “A secure web is here to stay,” ก.พ. 2018) ส่วน TLS certificate ฟรีแบบอัตโนมัติจาก Let’s Encrypt ก็ตัดข้อแก้ตัวเรื่องต้นทุนที่เหลืออยู่ของการทำ HTTPS ทุก origin ไปเกือบหมด

property ใดบอกคุณว่าหน้าเว็บปัจจุบันรันอยู่ใน secure context หรือไม่?
origin ใดต่อไปนี้ที่ browser สมัยใหม่ถือว่าเป็น secure context?
service worker ลงทะเบียนที่ /dashboard/sw.js โดยไม่มี option หรือ header พิเศษ จะควบคุมหน้าใดบ้าง?
จะทำให้ service worker ที่ลงทะเบียนที่ /app/sw.js มี scope เป็น / (ทั้ง origin) ได้อย่างไร?