CODEMONDAY

Guest access — บันทึกการแก้ปัญหาจริง

คนนอกองค์กร (guest) เข้าพอร์ทัลไม่ได้ · สาเหตุ วิธีแก้ และสิ่งที่แก้ไปแล้วบน tenant · 26 ส.ค. 2026

สรุปสั้น การให้ guest ใช้พอร์ทัลได้ ต้องผ่าน 3 ด่าน ที่อยู่คนละหน้าจอกันหมด และไม่มีด่านไหนบอกว่าอีกด่านมีอยู่ · ด่านที่คนลืมบ่อยที่สุดคือด่านที่ 2 (App Catalog) เพราะมันไม่เกี่ยวกับ site ที่กำลังเปิดเลย

อาการที่เจอ ตามลำดับ

อาการสาเหตุจริงด่านที่
เปิด site ไม่ได้เลย guest ยังไม่มีสิทธิ์บน site (หรือ external sharing ปิด) 1
เปิด site ได้ แต่ web part ทุกตัวขึ้น Something went wrong · TECHNICAL DETAILS บอกแค่ [object Object] โหลดไฟล์ JavaScript ของ web part ไม่ได้ — ไฟล์อยู่ใน App Catalog ซึ่ง guest อ่านไม่ได้ 2
หน้าแรกใช้ได้ แต่กดเมนูไป site ฝ่ายแล้วขึ้น You need access site ฝ่ายเป็น site collection แยก มีสิทธิ์ของตัวเอง 3
ให้สิทธิ์ครบแล้ว บาง site เข้าได้ บาง site ยังไม่ได้ ทั้งที่ตั้งค่าเหมือนกัน external sharing ปิดอยู่เฉพาะบาง site · guest ได้สิทธิ์เป็นศูนย์ทันทีไม่ว่าจะอยู่กลุ่มไหน 4
เบาะแสที่ใช้แยกด่าน 2 ออกจากด่านอื่น ถ้าอ่านข้อมูลไม่ได้ โค้ดของเราจะขึ้นข้อความภาษาไทย เสมอ (เช่น “โหลดเนื้อหาโซนไม่ได้ (HTTP 403)”) เพราะเราเขียนข้อความ error ไว้เองทุกจุด · เห็นข้อความอังกฤษของ SharePoint เมื่อไหร่ แปลว่าพังก่อนโค้ดเราได้ทำงาน

ทำไม App Catalog ถึงกัน guest โดยไม่มีใครตั้งใจ

ไฟล์ bundle ของ SPFx ถูกเก็บใน App Catalog เพราะ config/package-solution.json ตั้งไว้ว่า:

"includeClientSideAssets": true

สิทธิ์ตั้งต้นของ App Catalog ที่อ่านมาจาก API จริงคือ:

Everyone except external users   →  Read
ReadOnlyAccessToTenantAdminSite  →  Read

Everyone except external users แปลตรงตัวว่ายกเว้น guest — คนในองค์กรจึงใช้ได้ปกติ ส่วน guest พังทุก web part และไม่มีข้อความไหนบอกว่าเกี่ยวกับ App Catalog เลย

สิ่งที่แก้ไปแล้วบน tenant นี้ · 26 ส.ค. 2026

ที่ทำอะไรวิธี
/sites/appcatalogให้ NTL - Employee สิทธิ์ Readrole assignment ตรง (site นี้ไม่มีกลุ่ม SharePoint)
/sites/ntl-hrใส่ NTL - Employee ในกลุ่ม VisitorsREST
/sites/ntl-itใส่ NTL - Employee ในกลุ่ม VisitorsREST
/sites/ntl-branchใส่ NTL - Employee ในกลุ่ม VisitorsREST
/sites/ntl-hr · /sites/ntl-itเปิด external sharing เป็น New and existing guestsSharePoint admin center → Active sites → Sharing

/sites/ntl-poc มี NTL - Employee เป็น Visitors และเปิด sharing อยู่ก่อนแล้ว

ผลตรวจหลังแก้ครบ — ถาม SharePoint ว่าคำนวณสิทธิ์ของ guest ได้เท่าไรในแต่ละ site:

/sites/ntl-poc      sharing=true   Low=138612833 High=176   ✓
/sites/ntl-hr       sharing=true   Low=138612833 High=176   ✓
/sites/ntl-it       sharing=true   Low=138612833 High=176   ✓
/sites/ntl-branch   sharing=true   Low=138612833 High=176   ✓
สถานะ: ปิดเคสแล้ว guest ที่อยู่ในกลุ่ม NTL - Employee เข้าได้ครบทั้ง 4 site และ web part ทำงานทุกตัว · ค่าที่วัดได้เท่ากันทุก site จึงยืนยันได้ว่าไม่ได้เหลือ site ไหนตกหล่น

ด่านที่ 4 · external sharing ของแต่ละ site (เจอทีหลังสุด)

หลังให้สิทธิ์กลุ่มครบทั้งสาม site ฝ่ายแล้ว ยังเข้าได้แค่ site เดียว · ตรวจด้วย API พบว่าต่างกันจุดเดียว:

GET {site}/_api/site?$select=ShareByEmailEnabled

/sites/ntl-branch   true    ← guest เข้าได้
/sites/ntl-hr       false   ← guest เข้าไม่ได้
/sites/ntl-it       false   ← guest เข้าไม่ได้

ยืนยันซ้ำด้วยการถาม SharePoint ว่าคำนวณสิทธิ์ของ guest คนนั้นได้เท่าไร:

GET {site}/_api/web/getusereffectivepermissions(@u)?@u='i:0%23.f|membership|<guest>'

/sites/ntl-poc      Low=138612833 High=176   ← มีสิทธิ์
/sites/ntl-hr       Low=0         High=0     ← ศูนย์ ทั้งที่อยู่ในกลุ่ม Visitors

external sharing ที่ปิดอยู่ ลบสิทธิ์ของ guest ทิ้งทั้งหมด ไม่ว่าจะให้สิทธิ์ไว้แค่ไหน · และ site ที่สร้างด้วยสคริปต์จะได้ค่านี้ตามค่าเริ่มต้นตอนสร้าง ซึ่งอาจไม่เหมือนกันทุกตัว

แก้: SharePoint admin center → Active sites → เลือก site → Sharing → New and existing guests → Save · ต้องทำทีละ site

เครื่องมือที่ตอบได้ตรงที่สุดเวลาไล่ปัญหาสิทธิ์ getusereffectivepermissions บอกว่า SharePoint คำนวณสิทธิ์ของคนคนนั้นได้เท่าไรจริง ๆ · ถ้าได้ 0 ทั้งที่หน้าจอสิทธิ์ดูถูกต้องหมด แปลว่ามีอะไรบางอย่างลบสิทธิ์ทิ้งทั้งชั้น ซึ่งมักเป็น external sharing หรือ Conditional Access ไม่ใช่การตั้ง permission ผิด

ทำไมหน้าจอแอดมินทำไม่ได้ แต่ REST ทำได้

วิธีทำซ้ำ (เครื่อง Mac ไม่ต้องมี PowerShell)

1

ขอ token ของ SharePoint

ใช้เครื่องมือใน repo · เปิดเบราว์เซอร์ให้ล็อกอินครั้งเดียว แล้วดึง token จากหน้า admin ที่ deploy ไว้

// ในโฟลเดอร์ ntl-admin
import { AUDIENCE, grabTokens } from "./scripts/browserToken.ts";
const t = await grabTokens({
  want: [AUDIENCE.sharepoint],
  startPath: "/pages",
  adminUrl: "https://sharepoint-admin.codemonday.io",
});
2

หา login name ของกลุ่ม (ถ้ายังไม่รู้)

POST {site}/_api/SP.UI.ApplicationPages.ClientPeoplePickerWebServiceInterface.ClientPeoplePickerSearchUser
{ "queryParams": { "QueryString": "NTL - Employee", "PrincipalType": 4,
  "PrincipalSource": 15, "MaximumEntitySuggestions": 20 } }

คืนค่า Key มาให้ ซึ่งก็คือ c:0t.c|tenant|<guid>

3

ผูก principal เข้ากับ site ก่อน

POST {site}/_api/web/ensureuser
{ "logonName": "c:0t.c|tenant|<guid>" }   // ได้ principal Id กลับมา
4

ให้สิทธิ์ · เลือกทางใดทางหนึ่ง

// ทาง A · site มีกลุ่ม Visitors อยู่แล้ว (site ทั่วไป)
GET  {site}/_api/web/associatedvisitorgroup
POST {site}/_api/web/sitegroups(<id>)/users
{ "__metadata": { "type": "SP.User" }, "LoginName": "c:0t.c|tenant|<guid>" }

// ทาง B · ไม่มีกลุ่ม เช่น App Catalog
GET  {site}/_api/web/roledefinitions/getbyname('Read')      // Read = 1073741826
POST {site}/_api/web/roleassignments/addroleassignment(principalid=<pid>,roledefid=<rid>)
5

อ่านกลับมายืนยัน — อย่าเชื่อผลของการเขียนอย่างเดียว

GET {site}/_api/web/roleassignments?$expand=Member,RoleDefinitionBindings

ถ้ามี guest หรือ site เพิ่มในอนาคต

ข้อควรรู้ก่อนเสนอลูกค้า ถ้าองค์กรจะให้ outsource หรือพาร์ตเนอร์ (ScoreCard ข้อ A002 พูดถึง Process Maker = outsource) เข้าใช้พอร์ทัลด้วย ต้องออกแบบเรื่อง guest ตั้งแต่ต้น · Everyone except external users เป็นค่าเริ่มต้นที่โผล่หลายที่ในSharePoint และตัด guest ออกเงียบ ๆ ทุกครั้งโดยไม่มี error ที่ชี้ต้นเหตุ