ผมต้องการให้เว็บไซต์หลายแห่งที่เป็นอิสระต่อกันทำงานเสมือนเป็นระบบบัญชีเดียว โดยไม่ลดทอนการแยกขอบเขตตามปกติของเบราว์เซอร์ ผู้ใช้สามารถลงชื่อเข้าใช้บนโดเมนหนึ่ง แล้วภายหลังเปิดอีกโดเมนโดยตรง คลิก Log In และใช้งานต่อได้โดยไม่ต้องกรอกข้อมูลรับรองอีกครั้ง
ข้อจำกัดสำคัญคือเว็บไซต์เหล่านี้อยู่บนรูทโดเมนที่เป็นอิสระต่อกัน คุกกี้เซสชันที่สร้างโดยรูทโดเมนหนึ่งไม่สามารถกำหนดขอบเขตให้ครอบคลุมอีกรูทโดเมนที่ไม่เกี่ยวข้องได้ง่าย ๆ การออกแบบเข้าใจได้ง่ายขึ้นมากเมื่อผมแยกสองแนวคิดที่มักถูกปะปนกันออกจากกัน คือ ตัวตนส่วนกลาง กับ เซสชันเบราว์เซอร์เฉพาะไซต์
โมเดลที่ได้ค่อนข้างตรงไปตรงมา: แบ็กเอนด์รับรู้ตัวตนบัญชีเดียวที่ใช้ร่วมกัน แต่ละโดเมนมีเซสชันที่ผูกกับโฮสต์ของตัวเอง และการส่งต่อแบบใช้ครั้งเดียวอายุสั้นช่วยให้ต้นทางที่เชื่อถือได้ซึ่งยืนยันตัวตนแล้ว อนุญาตให้ต้นทางที่เชื่อถือได้อีกแห่งสร้างเซสชันใหม่ได้
ตัวตนส่วนกลางไม่จำเป็นต้องใช้คุกกี้ร่วมกัน
บัญชีสามารถเป็นส่วนกลางได้ แม้เซสชันของเบราว์เซอร์จะเป็นของแต่ละไซต์
แต่ละไซต์เก็บคุกกี้เซสชันแบบจำกัดเฉพาะโฮสต์ คุกกี้ที่ออกโดยรูทโดเมนหนึ่งไม่สามารถขยายไปยังรูทโดเมนอื่นที่ไม่เกี่ยวข้องผ่านแอตทริบิวต์ Domain ได้ แอตทริบิวต์นี้ขยายขอบเขตคุกกี้ได้เฉพาะภายในลำดับชั้นของโดเมนที่โฮสต์ผู้ออกมีสิทธิ์ใช้งาน
สำหรับเซสชันที่ควรผูกอยู่กับโฮสต์เดียว คุกกี้ที่มีคำนำหน้า __Host- เหมาะกับงานนี้ แนวคิดคือ:
Set-Cookie: __Host-<session-cookie>=<opaque-token>; Path=/; Secure; HttpOnly; SameSite=Laxคุณสมบัติสำคัญอยู่ที่ขอบเขต:
- Site A อ่านคุกกี้เซสชันแบบจำกัดเฉพาะโฮสต์ของ Site B ไม่ได้
- Site B อ่านคุกกี้เซสชันแบบจำกัดเฉพาะโฮสต์ของ Site A ไม่ได้
- แต่ทั้งสองเซสชันยังสามารถอ้างอิงไปยังตัวตนเดียวกันในแบ็กเอนด์ได้
ดังนั้น SSO ข้ามโดเมนไม่ควรพยายามย้ายคุกกี้จากไซต์หนึ่งไปอีกไซต์ แต่ควรส่งหลักฐานชั่วคราวว่าต้นทางที่เชื่อถือได้ได้ยืนยันตัวตนของผู้ใช้แล้ว
รูปแบบการส่งต่อแบบใช้ครั้งเดียว
ต้นทางที่ยืนยันตัวตนแล้วเปิดให้เรียกการดำเนินการส่งต่อขนาดเล็กหนึ่งรายการ เบราว์เซอร์จะเรียกใช้เฉพาะเมื่อจำเป็นต้องเปลี่ยนการยืนยันตัวตนข้ามไซต์
แบ็กเอนด์สร้างตั๋วแบบสุ่มที่ปลอดภัยเชิงการเข้ารหัส ส่งค่าดิบกลับไปยังเบราว์เซอร์เพียงครั้งเดียว และเก็บเฉพาะแฮชพร้อมข้อมูลขั้นต่ำที่จำเป็นต่อการตรวจสอบการส่งต่อ:
{
tokenHash,
userId,
sourceOrigin,
targetSite,
returnPath,
expiresAt
}ตั๋วควรมีอายุสั้นและใช้ได้กับเป้าหมายเพียงแห่งเดียว คำขอออกตั๋วฝั่งต้นทางควรผ่านการยืนยันตัวตนแล้วและมีการป้องกัน CSRF ต้องตรวจสอบเป้าหมายที่ร้องขอและปลายทางที่จะกลับไปก่อนออกตั๋วใด ๆ
จากนั้นเบราว์เซอร์ส่ง POST ระดับบนสุดไปยังเป้าหมาย เป้าหมายใช้ตั๋ว ระบุตัวตนผู้ใช้ที่ใช้ร่วมกัน สร้างเซสชันภายในของตัวเอง แล้วเปลี่ยนเส้นทางเบราว์เซอร์ไปยังพาธที่ตั้งใจไว้
ในเชิงแนวคิด:
record = consumeOnce({
tokenHash: hash(ticket),
targetSite: currentSite,
sourceOrigin: requestOrigin,
notExpired: true
})
if (!record) deny()
user = loadSharedUser(record.userId)
createLocalSession(user)
redirect(record.returnPath)ตั๋วนี้ไม่ใช่เซสชันของไซต์เป้าหมาย แต่เป็นสิทธิ์แบบใช้ครั้งเดียวสำหรับสร้างเซสชันนั้น
การใช้ตั๋วแบบอะตอมมิกปิดช่องว่างสำหรับการเล่นซ้ำ
การใช้งานที่อ่อนแอกว่าจะโหลดตั๋ว ตรวจสอบ สร้างเซสชัน แล้วค่อยลบตั๋วภายหลัง วิธีนี้สร้างช่วงเวลาที่คำขอสองรายการอาจเห็นเรคคอร์ดเดียวกันซึ่งยังมีผลอยู่
การออกแบบที่ปลอดภัยกว่าคือรวมการตรวจสอบกับการทำให้ใช้ไม่ได้ไว้ในปฏิบัติการใช้ตั๋วแบบอะตอมมิกครั้งเดียว หากไม่มีเรคคอร์ดที่ตรงกัน การยืนยันตัวตนจะล้มเหลว หากได้เรคคอร์ดกลับมา ตั๋วนั้นจะถูกนำออกจากชุดที่ใช้งานได้แล้วก่อนที่จะสร้างเซสชัน
วิธีนี้ยังทำให้ไซต์เป้าหมายผูกหลายเงื่อนไขเข้ากับปฏิบัติการเดียวกันได้ ได้แก่ เป้าหมายที่คาดไว้ ต้นทางที่คาดไว้ แฮชของโทเค็น และเวลาหมดอายุ
การตรวจสอบ Origin ควรปฏิเสธเมื่อไม่ผ่าน
การผูกกับต้นทางเพิ่มคุณสมบัติที่มีประโยชน์อีกอย่าง: ตั๋วที่ออกผ่านต้นทางที่เชื่อถือได้แห่งหนึ่งไม่ควรถูกใช้จากต้นทางที่ไม่เกี่ยวข้อง
การตรวจสอบ Origin ควรเป็นส่วนหนึ่งของเงื่อนไขการใช้ตั๋วโดยตรง นั่นหมายความว่าคำขอจากต้นทางที่ไม่ถูกต้องจะไม่ใช้ตั๋วที่ยังมีผลไป และต้นทางที่ถูกต้องยังสามารถทำการส่งต่อให้เสร็จได้ในภายหลัง
มีข้อควรระวังสำคัญหนึ่งข้อ: เฮดเดอร์ Origin ไม่ได้รับประกันว่าจะมีสตริงต้นทางแบบปกติในทุกบริบทของเบราว์เซอร์ คำขอบางประเภทอาจมาโดยไม่มีค่าที่ใช้งานได้ และบริบทที่ไม่เปิดเผยต้นทางอาจให้ค่า Origin: null
กฎของผมคือให้ถือว่า Origin ที่หายไปหรือไม่เปิดเผยต้นทางทำให้การตรวจสอบ Origin ล้มเหลว เว้นแต่แอปพลิเคชันจะมีกลไกตรวจสอบทางเลือกที่ออกแบบไว้อย่างชัดเจน ไม่ควรยอมรับค่าตรงตัว null เป็นต้นทางที่เชื่อถือได้เพียงเพื่อความเข้ากันได้
นี่เป็นการเลือกแบบปฏิเสธโดยปริยาย หากโฟลว์ที่ถูกต้องจำเป็นต้องรองรับบริบทที่ไม่มี Origin ใช้งานได้ ก็ควรออกแบบข้อยกเว้นนั้นอย่างชัดเจน แทนที่จะลดความเข้มงวดของการตรวจสอบทั่วไปแบบเงียบ ๆ
อย่าใส่ข้อมูลรับรองสำหรับการส่งต่อไว้ใน URL
ผมเลือกส่งตั๋วชั่วคราวผ่านฟอร์ม POST ระดับบนสุด แทนการใส่ไว้ในสตริงคิวรี
ตัวอย่างฝั่งไคลเอนต์แบบทั่วไปคือ:
const form = document.createElement('form');
form.method = 'POST';
form.action = targetOrigin + '/auth/consume';
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'ticket';
input.value = ticket;
form.append(input);
document.body.append(form);
form.submit();ไซต์เป้าหมายรับตั๋วจากเนื้อหาคำขอ POST ที่คาดไว้ และปฏิเสธความพยายามเทียบเท่าที่ส่งตั๋วผ่าน URL
วิธีนี้ไม่ได้ทำให้ข้อมูลรับรองไม่มีอันตราย แต่ช่วยไม่ให้มันอยู่ใน URL สำหรับการนำทางทั่วไป และลดโอกาสเปิดเผยโดยไม่ตั้งใจผ่านประวัติ URL ลิงก์ที่ถูกคัดลอก ข้อมูลผู้อ้างอิง และระบบบันทึกล็อกที่อิง URL
ลิงก์ข้ามไซต์ของผู้ใช้ที่ยืนยันตัวตนแล้วสามารถยกระดับตัวเองได้
หากไซต์ปัจจุบันรู้อยู่แล้วว่าผู้ใช้ผ่านการยืนยันตัวตน ลิงก์ธรรมดาไปยังไซต์ที่เชื่อถือได้อีกแห่งก็สามารถทำการส่งต่อได้เมื่อเหมาะสม
ลิงก์ยังควรมี href จริง SSO เป็นส่วนเสริมของการนำทางตามปกติ ไม่ใช่สิ่งที่มาแทนที่
โฟลว์ฝั่งไคลเอนต์แบบย่อมีลักษณะดังนี้:
if (session.status !== 'authenticated') {
return; // ordinary browser navigation
}
if (!isTrustedTarget(destination)) {
return;
}
event.preventDefault();
const ticket = await requestHandoff({
targetSite,
returnPath: destinationPath
});
postTicketToTarget(ticket);เวอร์ชันสำหรับใช้งานจริงควรป้องกันการส่งซ้ำ ตรวจสอบเป้าหมายกับทะเบียนไซต์ที่เชื่อถือได้ และกลับไปใช้การนำทางปกติหากไม่สามารถสร้างการส่งต่อได้
พฤติกรรมดั้งเดิมของเบราว์เซอร์ควรยังคงเป็นพฤติกรรมดั้งเดิม การคลิกเมาส์ปุ่มกลาง การคลิกพร้อมปุ่มเสริม การดาวน์โหลด และลิงก์ที่ตั้งใจเปิดในบริบทการเรียกดูอื่น ไม่ควรถูกแปลงเป็นคำขอยืนยันตัวตนโดยเงียบ ๆ
ปุ่ม Log In บนไซต์ที่สองต้องมีผู้ให้สิทธิ์ที่กำหนดไว้
กรณีที่ยากกว่าเริ่มขึ้นเมื่อผู้ใช้เปิด Site B โดยตรง Site B ไม่มีเซสชันภายใน จึงถือว่าผู้เยี่ยมชมเป็นผู้ใช้แบบไม่ระบุตัวตนอย่างถูกต้อง แม้เบราว์เซอร์จะยังมีเซสชันที่ใช้ได้บน Site A
Site B ตรวจสอบคุกกี้แบบจำกัดเฉพาะโฮสต์ของ Site A ไม่ได้ และไม่สามารถเปลี่ยนเส้นทางไปยัง Site C ใด ๆ แล้วคาดหวังให้ไซต์นั้นค้นพบเซสชันของส่วนอื่นในระบบได้ แต่ละรูทโดเมนที่ไม่เกี่ยวข้องกันมองเห็นได้เฉพาะคุกกี้ที่เป็นของตัวเอง
ดังนั้นโฟลว์ Log In สำหรับผู้ใช้แบบไม่ระบุตัวตนจึงต้องมี ผู้ให้สิทธิ์ที่กำหนดไว้ ซึ่งอาจเป็นต้นทางยืนยันตัวตนหลัก หรือไซต์ที่เชื่อถือได้ซึ่งเลือกไว้โดยเฉพาะและคาดว่าจะถือเซสชันล็อกอินที่นำกลับมาใช้ซ้ำได้
สำหรับกรณีง่าย ๆ แบบ Site A → Site B ลำดับคือ:
- ผู้ใช้ลงชื่อเข้าใช้บน Site A
- ภายหลัง ผู้ใช้เปิด Site B โดยตรง
- Site B ไม่มีเซสชัน จึงแสดง Log In
- ผู้ใช้คลิก Log In
- เบราว์เซอร์นำทางระดับบนสุดไปยัง Site A ซึ่งเป็นผู้ให้สิทธิ์ที่เลือกไว้
- Site A อ่านคุกกี้เซสชันแบบจำกัดเฉพาะโฮสต์ของตัวเองที่มีอยู่แล้วได้
- เนื่องจากเซสชันนั้นยังมีผล Site A จึงออกการส่งต่อแบบใช้ครั้งเดียวสำหรับ Site B ทันที
- เบราว์เซอร์ส่งตั๋วไปยัง Site B ด้วย POST
- Site B สร้างเซสชันภายในของตัวเอง แล้วพาผู้ใช้กลับไปยังหน้าที่ร้องขอ
จากมุมมองของผู้ใช้ นี่คือการเปลี่ยนเส้นทางอย่างรวดเร็วไปยัง Site A แล้วกลับมา ไม่ต้องใช้รหัสผ่าน เพราะ Site A มีเซสชันที่มันได้รับอนุญาตให้มองเห็นอยู่แล้ว
ข้อจำกัดก็สำคัญไม่แพ้กัน: หากผู้ใช้ล็อกอินอยู่เฉพาะบน Site C การเปลี่ยนเส้นทาง Site B ไปยัง Site A จะไม่ช่วย เว้นแต่ Site A จะเป็นโบรกเกอร์ยืนยันตัวตนส่วนกลางจริง ๆ ที่มีความรู้เกี่ยวกับสถานะล็อกอินอย่างเป็นอิสระของตัวเอง ฐานข้อมูลผู้ใช้ที่ใช้ร่วมกันไม่ได้ทำให้เซสชันของเบราว์เซอร์ใช้ร่วมกันด้วย
ทำไมผู้ให้สิทธิ์หลักจึงทำให้ระบบง่ายขึ้น
เมื่อจำนวนไซต์อิสระเพิ่มขึ้น ผู้ให้สิทธิ์หลักช่วยตัดความจำเป็นในการค้นหาเซสชันข้ามโดเมนใด ๆ ออกไป
หากไม่มีผู้ให้สิทธิ์หลัก Site B ที่ยังไม่ระบุตัวตนจะต้องหาทางตัดสินว่าไซต์อื่นใดอาจมีคุกกี้ที่ใช้งานได้อยู่ในตอนนั้น Site A ตรวจคุกกี้ของ Site C ไม่ได้ Site C ตรวจคุกกี้ของ Site A ไม่ได้ และ Site B ก็ตรวจของทั้งสองไซต์ไม่ได้
ผู้ให้สิทธิ์หลักทำให้ความพยายามล็อกอินแบบไม่ระบุตัวตนทุกครั้งมีเส้นทางที่คาดเดาได้เพียงเส้นทางเดียว:
Site B
→ ผู้ให้สิทธิ์ยืนยันตัวตน
→ มีเซสชันของผู้ให้สิทธิ์หรือไม่?
มี → สร้างการส่งต่อแบบใช้ครั้งเดียว
ไม่มี → แสดงการล็อกอินตามปกติ
→ Site B ใช้ตั๋วส่งต่อ
→ Site B สร้างเซสชันภายในผู้ให้สิทธิ์ไม่จำเป็นต้องให้บริการส่วนอื่นของแอปพลิเคชัน หน้าที่ที่เกี่ยวข้องในที่นี้คือเป็นเจ้าของเซสชันยืนยันตัวตนที่ใช้ซ้ำได้ และออกการส่งต่อที่จำกัดขอบเขตอย่างเคร่งครัดให้แก่ไซต์ที่เชื่อถือได้
สำหรับระบบขนาดเล็ก ไซต์ที่มีอยู่แล้วสามารถรับบทบาทนี้ได้ สำหรับสถาปัตยกรรมที่กว้างขึ้น ต้นทางยืนยันตัวตนโดยเฉพาะอาจทำให้โมเดลความเชื่อถือเข้าใจง่ายขึ้น
อย่าส่งผู้เยี่ยมชมทุกคนอ้อมผ่านผู้ให้สิทธิ์
ไม่ควรรันโฟลว์ของผู้ให้สิทธิ์โดยอัตโนมัติทุกครั้งที่มีการเปิดหน้าจากผู้ใช้แบบไม่ระบุตัวตน
การเปลี่ยนเส้นทางผู้เยี่ยมชมทุกคนไปยังอีกโดเมนเพียงเพื่อตรวจว่ามีเซสชันเดิมอยู่หรือไม่ ทำให้เกิดการนำทางที่ไม่จำเป็น เพิ่มความหน่วง เพิ่มรูปแบบความล้มเหลว ทำให้ข้อมูลวิเคราะห์มีสัญญาณรบกวนมากขึ้น และทำให้พฤติกรรมแคชซับซ้อนขึ้น
วิธีนี้ยังไม่เหมาะกับหน้าสาธารณะที่ไวต่อ SEO โปรแกรมรวบรวมข้อมูลหรือผู้เยี่ยมชมทั่วไปที่ร้องขอ URL สาธารณะควรได้รับหน้านั้นโดยตรง แทนที่จะถูกบังคับให้อ้อมผ่านขั้นตอนยืนยันตัวตนก่อน
กฎที่ผมใช้คือ:
อย่าตรวจผู้ให้สิทธิ์ตอนโหลดหน้า ให้เริ่มการยืนยันตัวตนข้ามโดเมนเฉพาะหลังจากผู้ใช้สั่ง Log In อย่างชัดเจน หรือเมื่อมีการนำทางข้ามไซต์โดยตั้งใจจากสถานะที่ยืนยันตัวตนแล้ว
วิธีนี้ทำให้ทราฟฟิกสาธารณะเรียบง่าย ขณะเดียวกันก็ยังทำให้การยืนยันตัวตนรวดเร็วสำหรับผู้ใช้ที่มีเซสชันที่ใช้ได้อยู่แล้วบนผู้ให้สิทธิ์ที่เลือกไว้
การตรวจสอบพาธสำหรับกลับต้องละเอียดกว่าการเช็กคำนำหน้า
การส่งต่อมักต้องจำไว้ว่าเบราว์เซอร์ควรไปลงที่ใดหลังจากนั้น ค่านี้อาจกลายเป็นช่องโหว่การเปลี่ยนเส้นทางแบบเปิดได้ หากถูกปฏิบัติเป็น URL ใด ๆ ที่ไคลเอนต์ควบคุมได้
หากทำได้ การออกแบบที่ปลอดภัยที่สุดคือไม่รับ URL ปลายทางแบบเต็มเลย ตัวระบุสั้น ๆ หรือการแมปฝั่งเซิร์ฟเวอร์สำหรับปลายทางที่รู้จัก ช่วยลดปริมาณงานด้านการแยกวิเคราะห์และตรวจสอบ URL
หากจำเป็นต้องรับพาธสัมพัทธ์ ให้ตรวจสอบอย่างระมัดระวังและจำกัดให้อยู่ภายในไซต์เป้าหมาย
ตัวอย่างต่อไปนี้ตั้งใจลดรายละเอียดลง และไม่ใช่รูทีนตรวจสอบการเปลี่ยนเส้นทางที่สมบูรณ์:
function isSafeReturnPath(value) {
if (!value.startsWith('/')) return false;
if (value.startsWith('//')) return false;
if (value.includes('\\\\')) return false;
const base = 'https://example.invalid';
const parsed = new URL(value, base);
return parsed.origin === base;
}การใช้งานจริงยังต้องพิจารณาพฤติกรรมการถอดค่า ข้อมูลอินพุตที่ผิดรูป อักขระควบคุม การทำให้เป็นรูปแบบมาตรฐาน กฎการกำหนดเส้นทางของแอปพลิเคชัน และการแปลงใด ๆ ที่พร็อกซีหรือเฟรมเวิร์กอาจทำก่อนสร้างการเปลี่ยนเส้นทาง
เงื่อนไขด้านความปลอดภัยที่ต้องคงอยู่ง่ายกว่ารายละเอียดของตัวแยกวิเคราะห์: การส่งต่อเลือกตำแหน่งที่อนุญาตภายในแอปพลิเคชันเป้าหมายได้ แต่ห้ามเลือกปลายทางภายนอกใด ๆ ตามอำเภอใจ
กรณีล้มเหลวสำคัญกว่าสถานการณ์ปกติ
การล็อกอินสำเร็จพิสูจน์ได้เพียงว่าเส้นทางพื้นฐานทำงาน การทดสอบที่มีประโยชน์กว่าคือการทดสอบขอบเขตความเชื่อถือ
ผมตรวจสอบพฤติกรรมในกรณีต่าง ๆ เช่น:
- ต้นทางที่ยังไม่ยืนยันตัวตนพยายามออกการส่งต่อ
- ไม่มีการป้องกัน CSRF
- Origin หายไป ไม่เปิดเผยต้นทาง หรือไม่ตรงกับที่คาดไว้
- เป้าหมายไม่ได้รับความเชื่อถือ
- ปลายทางสำหรับกลับไม่ปลอดภัย
- ตั๋วถูกส่งไปยังเป้าหมายผิดแห่ง
- มีความพยายามใช้ตั๋วจาก Origin ที่ไม่ถูกต้อง
- การเล่นตั๋วซ้ำ
- ตั๋วหมดอายุ
- ผู้ใช้ไม่มีอยู่แล้ว
- ตั๋วถูกส่งผ่าน URL แทนที่จะอยู่ในเนื้อหาคำขอ POST ตามที่คาดไว้
มีเงื่อนไขที่ต้องคงอยู่สองข้อที่ควรทดสอบโดยตรงเป็นพิเศษ: คำขอที่ถูกปฏิเสธต้องไม่ทำลายตั๋วที่ยังใช้ได้สำหรับโฟลว์ที่ถูกต้องโดยไม่ตั้งใจ และการใช้ตั๋วสำเร็จต้องทำให้ตั๋วนั้นใช้ซ้ำไม่ได้ทันที
โปรโตคอลขยายระบบได้โดยไม่ต้องทำอินทิเกรชันเป็นคู่ ๆ
การออกแบบการส่งต่อไม่เปลี่ยนโดยพื้นฐานเมื่อเพิ่มไซต์มากขึ้น
แต่ละไซต์ที่เข้าร่วมต้องใช้สัญญาขนาดเล็กชุดเดียวกัน:
- ตัวระบุเชิงตรรกะที่คงที่
- Origin สาธารณะที่เชื่อถือได้
- สิทธิ์เข้าถึงตัวตนบัญชีที่ใช้ร่วมกัน
- ปฏิบัติการออกการส่งต่อสำหรับต้นทางที่ยืนยันตัวตนแล้ว
- ปฏิบัติการใช้การส่งต่อ
- ทะเบียนไซต์ที่เชื่อถือได้
- เซสชันภายในที่ผูกกับโฮสต์
- นโยบายปลายทางสำหรับกลับที่ปลอดภัย
- และผู้ให้สิทธิ์ที่กำหนดไว้สำหรับโฟลว์ Log In แบบไม่ระบุตัวตน
ทางเลือกสำคัญในการขยายระบบคือหลีกเลี่ยงตรรกะยืนยันตัวตนแบบกำหนดเองเป็นคู่ ๆ ไซต์ที่ยืนยันตัวตนแล้วสามารถออกการส่งต่อชนิดเดียวกันให้ไซต์ที่เชื่อถือได้อีกแห่ง ส่วนไซต์ที่ยังไม่ระบุตัวตนสามารถส่งเบราว์เซอร์ไปยังผู้ให้สิทธิ์ที่กำหนดไว้
จึงทำให้ระบบยึดอยู่กับโปรโตคอลเดียว แทนที่จะกลายเป็นเมทริกซ์ของกรณีพิเศษที่เพิ่มขึ้นเรื่อย ๆ
การพาสถานะล็อกอินข้ามไซต์เป็นคนละเรื่องกับการกระจายสถานะออกจากระบบ
เซสชันที่ผูกกับโฮสต์และเป็นอิสระต่อกันทำให้เห็นความแตกต่างที่มีประโยชน์อีกอย่าง: การทำให้สถานะล็อกอินพกพาข้ามไซต์ได้ไม่ได้กำหนดพฤติกรรมการออกจากระบบทั่วทั้งระบบโดยอัตโนมัติ
หากผู้ใช้ออกจากระบบจาก Site A เซสชันภายในของอีกไซต์อาจยังมีผลอยู่ เว้นแต่แบ็กเอนด์จะเพิกถอนโดยตั้งใจ นี่คือนโยบายการจัดการเซสชัน ไม่ใช่ความล้มเหลวของกลไกส่งต่อ SSO
ระบบสามารถเลือกได้ระหว่างการออกจากระบบเฉพาะไซต์ การออกจากระบบทั้งหมด หรือการจัดการเซสชันแบบชัดเจน การแยกการตัดสินใจนี้ออกจากกลไกส่งต่อช่วยให้เข้าใจทั้งสองกลไกได้ง่ายขึ้น
โมเดลที่ผมใช้
สถาปัตยกรรมง่ายขึ้นเมื่อผมหยุดอธิบายปัญหาว่าเป็น “การแชร์คุกกี้ล็อกอินข้ามโดเมน”
มีตัวตนส่วนกลางเพียงหนึ่งเดียว แต่ละโดเมนมีเซสชันเบราว์เซอร์ของตัวเอง และการส่งต่อแบบใช้ครั้งเดียวอายุสั้นทำให้ต้นทางที่เชื่อถือได้ซึ่งยืนยันตัวตนแล้วสามารถอนุญาตให้สร้างเซสชันภายในอีกเซสชันหนึ่งได้
สำหรับการเข้าสู่ไซต์ที่ยังไม่ระบุตัวตนโดยตรง มีกฎเพิ่มอีกข้อที่สำคัญ: ต้องส่งเบราว์เซอร์ไปยังผู้ให้สิทธิ์ที่มองเห็นเซสชันเดิมของผู้ใช้ได้จริง หากผู้ใช้เคยลงชื่อเข้าใช้บนผู้ให้สิทธิ์นั้น การไปกลับอาจแทบมองไม่เห็น หากผู้ให้สิทธิ์ไม่มีเซสชันที่ใช้ได้ ก็ต้องกลับไปใช้การยืนยันตัวตนตามปกติ แทนที่จะทำเสมือนว่ามองเห็นเซสชันของโดเมนอื่นที่ไม่เกี่ยวข้องได้
โมเดลนี้รักษาขอบเขตโดเมนของเบราว์เซอร์ หลีกเลี่ยงการเปลี่ยนเส้นทางเพื่อยืนยันตัวตนที่ไม่จำเป็นสำหรับผู้เยี่ยมชมสาธารณะ และยังให้ประสบการณ์ตามที่ต้องการ: ลงชื่อเข้าใช้บนผู้ให้สิทธิ์ ภายหลังเปิดไซต์ที่เชื่อถือได้อีกแห่ง คลิก Log In แล้วกลับมาพร้อมเซสชันภายในใหม่โดยไม่ต้องกรอกรหัสผ่านอีกครั้ง