กลับไปที่บล็อก
7 ตุลาคม 2569Sergei Solod31 นาทีในการอ่าน

วิธีสร้าง SSO ข้ามโดเมนระหว่างรูทโดเมนที่เป็นอิสระต่อกัน

สถาปัตยกรรมเชิงปฏิบัติสำหรับการยืนยันตัวตนแบบรวมศูนย์ข้ามรูทโดเมนที่เป็นอิสระต่อกัน โดยใช้เซสชันแบบจำกัดเฉพาะโฮสต์ ผู้ให้สิทธิ์ยืนยันตัวตนที่กำหนดไว้ และโทเค็นส่งต่อแบบใช้ครั้งเดียวอายุสั้น โดยไม่บังคับให้ผู้เยี่ยมชมทุกคนต้องผ่านการเปลี่ยนเส้นทางเพื่อยืนยันตัวตน

SSO ข้ามโดเมนการยืนยันตัวตนความปลอดภัยของเซสชันความปลอดภัยเว็บสถาปัตยกรรมเว็บ

ผมต้องการให้เว็บไซต์หลายแห่งที่เป็นอิสระต่อกันทำงานเสมือนเป็นระบบบัญชีเดียว โดยไม่ลดทอนการแยกขอบเขตตามปกติของเบราว์เซอร์ ผู้ใช้สามารถลงชื่อเข้าใช้บนโดเมนหนึ่ง แล้วภายหลังเปิดอีกโดเมนโดยตรง คลิก 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 ลำดับคือ:

  1. ผู้ใช้ลงชื่อเข้าใช้บน Site A
  2. ภายหลัง ผู้ใช้เปิด Site B โดยตรง
  3. Site B ไม่มีเซสชัน จึงแสดง Log In
  4. ผู้ใช้คลิก Log In
  5. เบราว์เซอร์นำทางระดับบนสุดไปยัง Site A ซึ่งเป็นผู้ให้สิทธิ์ที่เลือกไว้
  6. Site A อ่านคุกกี้เซสชันแบบจำกัดเฉพาะโฮสต์ของตัวเองที่มีอยู่แล้วได้
  7. เนื่องจากเซสชันนั้นยังมีผล Site A จึงออกการส่งต่อแบบใช้ครั้งเดียวสำหรับ Site B ทันที
  8. เบราว์เซอร์ส่งตั๋วไปยัง Site B ด้วย POST
  9. 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 แล้วกลับมาพร้อมเซสชันภายในใหม่โดยไม่ต้องกรอกรหัสผ่านอีกครั้ง