สร้างแอป SaaS หลายผู้เช่า: คู่มือฉบับสมบูรณ์ตั้งแต่การออกแบบจนถึงการใช้งาน (Build a multi-tenant SaaS application: A complete guide from design to implementation)
แอปอย่าง Notion, Slack หรือ Figma ถูกสร้างขึ้นอย่างไร? แอป SaaS หลายผู้เช่าเหล่านี้ดูเหมือนใช้งานง่าย แต่ถ้าคุณต้องสร้างเองล่ะ? นั่นคืออีกเรื่องหนึ่ง
ตอนที่ฉันคิดจะสร้างระบบที่ซับซ้อนแบบนี้ สมองแทบระเบิด:
- ผู้ใช้ต้องมีตัวเลือกการลงชื่อเข้าใช้หลายแบบ (อีเมล, Google, GitHub)
- ผู้ใช้แต่ละคนสามารถสร้างและเป็นสมาชิกในหลายองค์กรได้
- ระดับสิทธิ์ที่แตกต่างกันในแต่ละองค์กร
- องค์กรขนาดใหญ่ต้องการการเข้าร่วมอัตโนมัติสำหรับโดเมนอีเมลเฉพาะ
- ต้องมี MFA สำหรับการดำเนินการที่สำคัญ
- และอื่น ๆ อีกมาก...
"หัวหน้า เดี๋ยวค่อยคุยเรื่องออกแบบผลิตภัณฑ์อีกสองอาทิตย์นะ ตอนนี้ติดหล่มอยู่"
แต่พอได้ลงมือทำจริง ๆ ฉันพบว่ามันไม่ได้ยากอย่างที่คิด!
ฉันเพิ่ง สร้างระบบที่มีฟีเจอร์เหล่านี้ทั้งหมดได้อย่างง่ายดาย!



ฉันจะอธิบายให้คุณเห็นทีละขั้นตอนว่าควรออกแบบและสร้างระบบแบบนี้อย่างไรตั้งแต่เริ่มต้น — แล้วคุณจะทึ่งว่ามันง่ายแค่ไหนในปี 2025 ด้วยเครื่องมือสมัยใหม่และแนวทางสถาปัตยกรรมที่ถูกต้อง
ซอร์สโค้ดทั้งหมดอยู่ใน Github Repo นี้ ไปดูกันเลย!
เราจะเริ่มจากผลิตภัณฑ์ SaaS ด้านเอกสาร AI ชื่อ DocuMind
DocuMind คือผลิตภัณฑ์ SaaS ด้านเอกสาร AI ที่ออกแบบด้วยโมเดลหลายผู้เช่า เพื่อรองรับทั้งผู้ใช้เดี่ยว ธุรกิจขนาดเล็ก และองค์กรขนาดใหญ่
แพลตฟอร์มนี้ให้ความสามารถ AI อันทรงพลังสำหรับการจัดการเอกสาร เช่น การสรุปอัตโนมัติ การดึงประเด็นสำคัญ และการแนะนำเนื้อหาอัจฉริยะภายในองค์กร
ฟีเจอร์ที่จำเป็นสำหรับการยืนยันตัวตน (Authentication) และการอนุญาต (Authorization) ของ SaaS มีอะไรบ้าง?
ก่อนอื่น มาทบทวนความต้องการที่จำเป็นกันก่อน คุณต้องการฟีเจอร์อะไรบ้าง?
สถาปัตยกรรมหลายผู้เช่า (Multi-tenant architecture)
เพื่อรองรับสถาปัตยกรรมหลายผู้เช่า คุณต้องมีเลเยอร์เอนทิตีที่เรียกว่า องค์กร (Organization) ลองนึกภาพว่ามีผู้ใช้กลุ่มเดียวที่สามารถเข้าถึงหลาย workspace ได้ องค์กรแต่ละแห่งคือ workspace และผู้ใช้จะมีตัวตนเดียวขณะเข้าถึง workspace (องค์กร) ต่าง ๆ ตามบทบาทที่ได้รับ
นี่เป็นฟีเจอร์ที่ใช้กันอย่างแพร่หลายในผู้ให้บริการการยืนยันตัวตน (Authentication) องค์กรในระบบจัดการเอกลักษณ์จะตรงกับ workspace, project หรือ tenant ในแอป SaaS ของคุณ

สมาชิกภาพ (Membership)
สมาชิก (Member) เป็นแนวคิดชั่วคราวที่ใช้ระบุสถานะสมาชิกของเอกลักษณ์ในองค์กร
ตัวอย่างเช่น Sarah สมัครใช้งานแอปของคุณด้วยอีเมล sarah@gmail.com เธอสามารถอยู่ใน workspace ต่าง ๆ ได้ หาก Sarah อยู่ใน Workspace A แต่ไม่อยู่ใน Workspace B เธอถือเป็นสมาชิกของ Workspace A แต่ไม่ใช่ Workspace B
การออกแบบบทบาทและสิทธิ์ (Role and permission design)
ในสถาปัตยกรรมหลายผู้เช่า ผู้ใช้ต้องมี บทบาท (Role) พร้อม สิทธิ์ (Permission) เฉพาะเพื่อเข้าถึงทรัพยากรของ tenant
สิทธิ์ (Permission) คือการควบคุมการเข้าถึงอย่างละเอียด เช่น read: order หรือ write: order เพื่อกำหนดว่าทำอะไรกับทรัพยากรใดได้บ้าง
บทบาท (Role) คือชุดของสิทธิ์ที่กำหนดให้กับสมาชิกในสภาพแวดล้อมหลายผู้เช่า
คุณต้องกำหนดบทบาทและสิทธิ์เหล่านี้ จากนั้นกำหนดบทบาทให้กับผู้ใช้ และบางครั้งอาจมีการกำหนดอัตโนมัติ เช่น
- ผู้ใช้ที่เข้าร่วมองค์กรจะได้รับบทบาท member อัตโนมัติ
- ผู้ใช้คนแรกที่สร้าง workspace จะได้รับบทบาท admin อัตโนมัติ
กระบวนการสมัครและเข้าสู่ระบบ (Sign-up and login flow)
ต้องแน่ใจว่ากระบวนการสมัครและยืนยันตัวตนใช้งานง่ายและปลอดภัย รวมถึงตัวเลือกพื้นฐานดังนี้:
- ลงชื่อเข้าใช้ด้วยอีเมลและรหัสผ่าน: วิธีดั้งเดิม
- เข้าสู่ระบบแบบไม่ใช้รหัสผ่าน: ใช้รหัสยืนยันทางอีเมลเพื่อความสะดวกและปลอดภัย
- การจัดการบัญชี: ศูนย์บัญชีสำหรับอัปเดตอีเมล รหัสผ่าน และรายละเอียดอื่น ๆ
- เข้าสู่ระบบโซเชียล: ตัวเลือกอย่าง Google และ GitHub เพื่อความรวดเร็ว
- การยืนยันตัวตนหลายปัจจัย (MFA): เพิ่มความปลอดภัยด้วยแอปยืนยันตัวตน เช่น Duo
การสร้าง tenant และเชิญสมาชิก (Tenant creation and invitation)
ในแอป SaaS หลายผู้เช่า ความแตกต่างสำคัญใน flow ของผู้ใช้คือการรองรับการสร้าง tenant และเชิญสมาชิก ซึ่งต้องวางแผนและดำเนินการอย่างรอบคอบเพราะมีผลต่อการเปิดใช้งานและการเติบโตของผลิตภัณฑ์
นี่คือตัวอย่าง flow ที่พบบ่อย:
| ประเภทผู้ใช้ | จุดเริ่มต้น |
|---|---|
| บัญชีใหม่ | เข้าจากหน้าลงชื่อเข้าใช้ / สมัครเพื่อสร้าง tenant ใหม่ |
| บัญชีเดิม | สร้าง tenant ใหม่ในผลิตภัณฑ์ |
| บัญชีเดิมได้รับเชิญ tenant ใหม่ | เข้าจากหน้าลงชื่อเข้าใช้ / สมัคร |
| บัญชีเดิมได้รับเชิญ tenant ใหม่ | เข้าจากอีเมลเชิญ |
| บัญชีใหม่ได้รับเชิญ tenant ใหม่ | เข้าจากหน้าลงชื่อเข้าใช้ / สมัคร |
| บัญชีใหม่ได้รับเชิญ tenant ใหม่ | เข้าจากอีเมลเชิญ |
นี่คือสถานการณ์ที่พบในแอป SaaS เกือบทุกตัว ใช้เป็นแนวทางให้ทีมผลิตภัณฑ์และออกแบบของคุณ และสามารถสร้าง flow ของคุณเองได้ตามต้องการ






สถาปัตยกรรมเทคนิคและการออกแบบระบบ (Technical architecture and system design)
เมื่อเข้าใจความต้องการของผลิตภัณฑ์แล้ว มาดูวิธีการใช้งานกัน
กำหนดกลยุทธ์การยืนยันตัวตน (Define authentication strategy)
การยืนยันตัวตน (Authentication) ดูน่ากลัว ผู้ใช้ต้องการ:
- สมัคร / เข้าสู่ระบบด้วยอีเมลและรหัสผ่าน
- ลงชื่อเข้าใช้ด้วย Google / Github เพียงคลิกเดียว
- รีเซ็ตรหัสผ่านเมื่อจำไม่ได้
- การเข้าสู่ระบบทั้งทีมสำหรับลูกค้าองค์กร
- ...
แค่ฟีเจอร์พื้นฐานเหล่านี้ก็อาจใช้เวลาหลายสัปดาห์ในการพัฒนา
แต่ตอนนี้ เราไม่ต้องสร้างเองเลย!
ผู้ให้บริการ auth สมัยใหม่ (ในที่นี้จะเลือก Logto) ได้รวมฟีเจอร์เหล่านี้ไว้ให้แล้ว Flow การยืนยันตัวตนเป็นดังนี้:
จากงานพัฒนาหลายสัปดาห์เหลือแค่ 15 นาที Logto จัดการ flow ที่ซับซ้อนทั้งหมดให้เรา! ส่วนขั้นตอนการเชื่อมต่อจะอธิบายในหัวข้อ implementation ต่อไป ตอนนี้เราสามารถโฟกัสที่ฟีเจอร์หลักของ DocuMind ได้แล้ว!
สร้างสถาปัตยกรรมหลายผู้เช่า (Establish multi-tenant architecture)
ระบบองค์กร (Organization) ช่วยให้ผู้ใช้สร้างและเข้าร่วมองค์กรได้หลายแห่ง มาดูความสัมพันธ์หลัก ๆ กัน
ในระบบนี้ ผู้ใช้แต่ละคนสามารถอยู่ในหลายองค์กร และแต่ละองค์กรก็มีสมาชิกได้หลายคน
เปิดใช้งานการควบคุมการเข้าถึงในแอปหลายผู้เช่า (Enable access control in multi-tenant app)
การควบคุมการเข้าถึงตามบทบาท (RBAC) สำคัญต่อความปลอดภัยและการขยายตัวของแอป SaaS หลายผู้เช่า
ในแอปหลายผู้เช่า การออกแบบสิทธิ์และบทบาทมักจะเหมือนกัน เพราะมาจากการออกแบบผลิตภัณฑ์ เช่น ใน workspace หลายแห่ง มักจะมีบทบาท admin และ member Logto ในฐานะ auth provider มีการออกแบบ RBAC ระดับองค์กรดังนี้:
- นิยามสิทธิ์แบบรวมศูนย์: กำหนดสิทธิ์ในระดับระบบและใช้กับทุกองค์กร เพื่อให้จัดการสิทธิ์ได้ง่ายและสม่ำเสมอ
- แม่แบบองค์กร (Organization templates): กำหนดบทบาทและสิทธิ์ล่วงหน้าผ่านแม่แบบองค์กร ช่วยให้องค์กรใหม่ตั้งค่าได้ง่าย
ความสัมพันธ์ของสิทธิ์เป็นดังนี้:
เนื่องจากผู้ใช้แต่ละคนต้องมีบทบาทของตัวเองในแต่ละองค์กร ความสัมพันธ์ระหว่างบทบาทกับองค์กรจึงต้องสะท้อนบทบาทที่กำหนดให้ผู้ใช้แต่ละคน:
เราได้ออกแบบระบบองค์กรและระบบควบคุมการเข้าถึงแล้ว ตอนนี้ก็เริ่มสร้างผลิตภัณฑ์ของเราได้เลย!
Tech stack
ฉันเลือก stack ที่เหมาะกับผู้เริ่มต้นและยืดหยุ่น:
- Frontend: React (สามารถเปลี่ยนเป็น Vue / Angular / Svelte ได้ง่าย)
- Backend: Express (API เข้าใจง่าย)
ทำไมต้องแยก frontend กับ backend? เพราะสถาปัตยกรรมชัดเจน เรียนรู้ง่าย และเปลี่ยน stack ได้สะดวก และสำหรับ auth provider ฉันใช้ Logto เป็นตัวอย่าง
และสำหรับคู่มือนี้ รูปแบบนี้ใช้ได้กับ: frontend ใด ๆ, backend ใด ๆ และระบบ auth ใด ๆ
เพิ่ม flow การยืนยันตัวตน (Authentication) พื้นฐานให้แอปของคุณ
ขั้นตอนนี้ง่ายที่สุด แค่เชื่อมต่อ Logto กับโปรเจกต์ของเรา จากนั้นตั้งค่าวิธี login / สมัครใน Logto Console ตามต้องการ
ติดตั้ง Logto ในแอปของคุณ
ก่อนอื่น เข้าสู่ระบบ Logto Cloud สมัครฟรีถ้ายังไม่มีบัญชี สร้าง Development Tenant สำหรับทดสอบ
ใน Tenant Console คลิกปุ่ม "Application" ทางซ้าย แล้วเลือก React เพื่อเริ่มสร้างแอปของเรา
ทำตามคู่มือในหน้านั้น คุณจะเชื่อมต่อ Logto ได้ในประมาณ 5 นาที!
นี่คือตัวอย่างโค้ดการเชื่อมต่อ:
const config: LogtoConfig = {
endpoint: "<YOUR_LOGTO_ENDPOINT>",
appId: "<YOUR_LOGTO_APP_ID>",
};
function App() {
return (
<LogtoProvider config={config}>
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100">
<Routes>
{/* Callback นี้จัดการ redirect login จาก Logto */}
<Route path="/callback" element={<Callback />} />
<Route path="/*" element={<AppContent />} />
</Routes>
</div>
</LogtoProvider>
);
}
function AppContent() {
const { isAuthenticated } = useLogto();
if (!isAuthenticated) {
// แสดง landing page สำหรับผู้ใช้ที่ยังไม่ยืนยันตัวตน
return <Landing />;
}
// แสดงแอปหลักสำหรับผู้ใช้ที่ยืนยันตัวตนแล้ว
return (
<Routes>
{/* Dashboard แสดงองค์กรทั้งหมดที่มี */}
<Route path="/" element={<Dashboard />} />
{/* หน้าองค์กรหลังคลิกองค์กรใน Dashboard */}
<Route path="/:orgId" element={<Organization />} />
</Routes>
);
}

ทริคที่มีประโยชน์: หน้า login ของเรามีทั้งปุ่ม Sign in และ Register ปุ่ม Register จะนำไปยังหน้าสมัครของ Logto โดยตรง ใช้ฟีเจอร์ first screen ของ Logto เพื่อกำหนดว่าผู้ใช้จะเห็นขั้นตอนไหนก่อนใน flow
คุณสามารถตั้งค่าให้ไปที่หน้าสมัครโดยตรงหากผลิตภัณฑ์ของคุณคาดว่าจะมีผู้ใช้ใหม่จำนวนมาก
function LandingPage() {
const { signIn } = useLogto();
return (
<div className="landing-container">
<div className="auth-buttons">
<button
className="sign-in-button"
onClick={() => {
signIn({
redirectUri: '<YOUR_APP_CALLBACK_URL>',
});
}}
>
Sign In
</button>
<button
className="register-button"
onClick={() => {
signIn({
redirectUri: '<YOUR_APP_CALLBACK_URL>',
firstScreen: 'register',
});
}}
>
Register
</button>
</div>
</div>
);
}
หลังคลิก login คุณจะไปที่หน้า login ของ Logto เมื่อลงชื่อเข้าใช้ (หรือสมัคร) สำเร็จ ยินดีด้วย! แอปของคุณมีผู้ใช้คนแรกแล้ว (ก็คือคุณ!)
และเรียกฟังก์ชัน signOut จาก hook useLogto เพื่อออกจากระบบเมื่อคุณต้องการ
function SignOutButton() {
const { signOut } = useLogto();
return <button onClick={() => signOut('<YOUR_POST_LOGOUT_REDIRECT_URL>')}>Sign Out</button>;
}
ปรับแต่งวิธีลงชื่อเข้าใช้และสมัคร (Customize sign in and sign up methods)
ใน Logto Console คลิก "Sign-in & account" ทางเมนูซ้าย แล้วคลิกแท็บ "Sign-up and sign-in" ในหน้านี้ ทำตามคำแนะนำเพื่อกำหนดวิธี login / สมัครของ Logto

และ flow การลงชื่อเข้าใช้จะเป็นแบบนี้:
เปิดใช้งานการยืนยันตัวตนหลายปัจจัย (Enable multi-factor authentication)
ใน Logto การเปิด MFA ทำได้ง่าย แค่คลิกปุ่ม "Multi-factor auth" ใน Logto Console แล้วเปิดใช้งานในหน้า Multi-factor authentication

และ flow MFA จะเป็นแบบนี้:


ทุกอย่างง่ายมาก! เราตั้งค่าระบบยืนยันตัวตนผู้ใช้ที่ซับซ้อนได้ในไม่กี่นาที!
เพิ่มประสบการณ์องค์กรหลายผู้เช่า (Adding multi-tenant organization experience)
ตอนนี้เรามีผู้ใช้คนแรกแล้ว! แต่ผู้ใช้นี้ยังไม่ได้อยู่ในองค์กรใด และเรายังไม่ได้สร้างองค์กรเลย
Logto มีฟีเจอร์ multi-tenancy ในตัว คุณสามารถสร้างองค์กรได้ไม่จำกัดใน Logto แต่ละองค์กรมีสมาชิกได้หลายคน
ผู้ใช้แต่ละคนสามารถดึงข้อมูลองค์กรของตัวเองจาก Logto ได้ สิ่งนี้ช่วยให้รองรับ multi-tenancy
ดึงข้อมูลองค์กรของผู้ใช้ (Get a user's organization information)
เพื่อดึงข้อมูลองค์กรของผู้ใช้จาก Logto ทำสองขั้นตอนนี้:
ประกาศการเข้าถึงข้อมูลองค์กรใน Logto Config โดยตั้งค่า scopes และ resources ที่เหมาะสม
import { UserScope, ReservedResource } from "@logto/react";
const config: LogtoConfig = {
endpoint: "<YOUR_LOGTO_ENDPOINT>",
appId: "<YOUR_LOGTO_APP_ID>",
scopes: [UserScope.Organizations], // ค่า: "urn:logto:scope:organizations"
resources: [ReservedResource.Organization], // ค่า: "urn:logto:resource:organizations"
};
ใช้เมธอด fetchUserInfo ของ Logto เพื่อดึงข้อมูลผู้ใช้ รวมถึงข้อมูลองค์กร
function Dashboard() {
// ดึงข้อมูลผู้ใช้
const { fetchUserInfo } = useLogto();
const [organizations, setOrganizations] = useState<OrganizationData[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadOrganizations = async () => {
try {
setLoading(true);
// ดึงข้อมูลผู้ใช้
const userInfo = await fetchUserInfo();
// ดึงข้อมูลองค์กรของผู้ใช้
const organizationData = userInfo?.organization_data || [];
setOrganizations(organizationData);
} catch (error) {
console.error('Failed to fetch organizations:', error);
} finally {
setLoading(false);
}
};
loadOrganizations();
}, [fetchUserInfo]);
if (loading) {
return <div>Loading...</div>;
}
if (organizations.length === 0) {
return <div>คุณยังไม่ได้เป็นสมาชิกขององค์กรใดเลย</div>;
}
return <div>Organizations: {organizations.map(org => org.name).join(', ')}</div>;
}
หลังจากทำขั้นตอนนี้แล้ว คุณต้องออกจากระบบและเข้าสู่ระบบใหม่ เพราะเราได้เปลี่ยน scope และ resource ที่ร้องขอ
ตอนนี้คุณยังไม่ได้สร้างองค์กรใด ๆ ผู้ใช้ก็ยังไม่ได้เข้าร่วมองค์กรใด Dashboard จะแสดงว่า "You don’t have any organization yet"

ต่อไป เราจะสร้างองค์กรให้ผู้ใช้ของเราและเพิ่มเขาเข้าไป
ขอบคุณ Logto ที่ช่วยให้เราไม่ต้องสร้างความสัมพันธ์องค์กรที่ซับซ้อนเอง แค่สร้างองค์กรใน Logto และเพิ่มผู้ใช้เข้าไป Logto จะจัดการความซับซ้อนทั้งหมดให้ มีสองวิธีในการสร้างองค์กร:
- สร้างองค์กรผ่าน Logto Console ด้วยตนเอง
- ใช้ Logto Management API เพื่อสร้างองค์กร โดยเฉพาะเมื่อออกแบบ flow SaaS ที่ให้ผู้ใช้สร้างองค์กร (workspace) เอง
สร้างองค์กรใน Logto console (Create organization in Logto console)
คลิกเมนู "Organizations" ทางซ้ายใน Logto Console แล้วสร้างองค์กร
ตอนนี้คุณมีองค์กรแรกแล้ว

ต่อไป เพิ่มผู้ใช้เข้าองค์กรนี้
ไปที่หน้ารายละเอียดองค์กร สลับไปแท็บ Members คลิกปุ่ม "+ Add member" เลือกผู้ใช้ที่ login จากลิสต์ซ้าย คลิก "Add members" ด้านขวาล่าง ตอนนี้คุณเพิ่มผู้ใช้เข้าองค์กรนี้สำเร็จแล้ว

รีเฟรชหน้าแอปของคุณ จะเห็นว่าผู้ใช้เป็นสมาชิกขององค์กรแล้ว!

สร้างประสบการณ์สร้างองค์กรด้วยตนเอง (Implement self-serve organization creation experience)
การสร้างองค์กรใน console ยังไม่พอ แอป SaaS ของคุณต้องมี flow ให้ผู้ใช้ปลายทางสร้างและจัดการ workspace ของตัวเองได้ง่าย ๆ ใช้ Logto Management API เพื่อสร้างฟีเจอร์นี้
ดูคู่มือ Interact with Management API เพื่อเชื่อมต่อ API กับ Logto
เข้าใจ flow การโต้ตอบ auth ขององค์กร (Understand organization auth interaction flow)
ขอยกตัวอย่าง flow การสร้างองค์กร กระบวนการเป็นดังนี้:
flow นี้มีข้อกำหนดด้านการยืนยันตัวตนสองข้อหลัก:
- ปกป้อง API ของ backend service:
- Frontend ที่เข้าถึง Backend Service API ต้องยืนยันตัวตน
- API endpoint ถูกปกป้องด้วยการตรวจสอบ Logto Access Token ของผู้ใช้
- รับรองว่ามีแต่ผู้ใช้ที่ยืนยันตัวตนแล้วเท่านั้นที่เข้าถึงบริการได้
- เข้าถึง Logto Management API:
- Backend Service ต้องเรียก Logto Management API อย่างปลอดภัย
- ทำตามคู่มือ Interact with Management API
- ใช้การยืนยันตัวตนแบบเครื่องต่อเครื่อง (Machine-to-Machine) เพื่อขอสิทธิ์เข้าถึง
ปกป้อง backend API ของคุณ (Protect your backend API)
ก่อนอื่น สร้าง API endpoint ใน backend service สำหรับสร้างองค์กร
app.post('/organizations', async (req, res) => {
// Implementation using Logto Management API
// ...
});
API ของ backend service อนุญาตเฉพาะผู้ใช้ที่ยืนยันตัวตนแล้วเท่านั้น ต้องใช้ Logto เพื่อปกป้อง API และต้องรู้ข้อมูลผู้ใช้ปัจจุบัน (เช่น user ID)
ในแนวคิดของ Logto (และ OAuth 2.0) backend service ของเราทำหน้าที่เป็น resource server ผู้ใช้เข้าถึง DocuMind resource server ด้วย Access token จาก frontend resource server จะตรวจสอบ token นี้ ถ้าถูกต้องจะคืน resource ที่ร้องขอ
สร้าง API Resource เพื่อแทน backend service ของเรา
ไปที่ Logto Console
- คลิกปุ่ม "API resources" ทางขวา
- คลิก "Create API resource" เลือก Express ใน popup
- ใส่ "DocuMind API" เป็นชื่อ API ใช้ "https://api.documind.com" เป็น API identifier
- คลิกสร้าง
ไม่ต้องกังวลกับ URL API identifier นี้ มันเป็นแค่ตัวระบุเฉพาะสำหรับ API ของคุณใน Logto ไม่เกี่ยวกับ URL backend service จริง
คุณจะเห็นคู่มือการใช้ API resource สามารถทำตามนั้นหรือทำตามขั้นตอนด้านล่าง
สร้าง middleware requireAuth เพื่อปกป้อง endpoint POST /organizations
const { createRemoteJWKSet, jwtVerify } = require('jose');
const getTokenFromHeader = (headers) => {
const { authorization } = headers;
const bearerTokenIdentifier = 'Bearer';
if (!authorization) {
throw new Error('Authorization header missing');
}
if (!authorization.startsWith(bearerTokenIdentifier)) {
throw new Error('Authorization token type not supported');
}
return authorization.slice(bearerTokenIdentifier.length + 1);
};
const requireAuth = (resource) => {
if (!resource) {
throw new Error('Resource parameter is required for authentication');
}
return async (req, res, next) => {
try {
// ดึง token
const token = getTokenFromHeader(req.headers);
const { payload } = await jwtVerify(
token,
createRemoteJWKSet(new URL(process.env.LOGTO_JWKS_URL)),
{
issuer: process.env.LOGTO_ISSUER,
audience: resource,
}
);
// เพิ่มข้อมูลผู้ใช้ใน request
req.user = {
id: payload.sub,
};
next();
} catch (error) {
console.error('Auth error:', error);
res.status(401).json({ error: 'Unauthorized' });
}
};
};
module.exports = {
requireAuth,
};
เพื่อใช้ middleware นี้ ต้องมี environment variables เหล่านี้:
- LOGTO_JWKS_URL
- LOGTO_ISSUER
ดึงค่าจาก OpenID Configuration endpoint ของ Logto tenant ของคุณ ไปที่ https://<your-tenant-id>.logto.app/oidc/.well-known/openid-configuration จะพบข้อมูลใน JSON ที่ได้:
{
"jwks_uri": "<https://tenant-id.logto.app/oidc/jwks>",
"issuer": "<https://tenant-id.logto.app/oidc>"
}
ใช้ middleware requireAuth ใน endpoint POST /organizations
app.post('/organizations', requireAuth('<https://api.documind.com>'), async (req, res) => {
// Handle organization creation logic
// ...
});
วิธีนี้จะปกป้อง endpoint POST /organizations เฉพาะผู้ใช้ที่มี Logto access token ที่ถูกต้องเท่านั้นที่เข้าถึงได้
ตอนนี้เราสามารถดึง token จาก Logto ใน frontend ได้แล้ว ผู้ใช้สามารถสร้างองค์กรผ่าน backend service ของเราด้วย token นี้ middleware ยังให้ user ID มาด้วย ช่วยตอนเพิ่มผู้ใช้เข้าองค์กร
ในโค้ด frontend ให้ประกาศ API resource นี้ใน Logto config เพิ่ม identifier ลงใน resources array
const config: LogtoConfig = {
endpoint: "<YOUR_LOGTO_ENDPOINT>",
appId: "<YOUR_LOGTO_APP_ID>",
scopes: [UserScope.Organizations],
resources: [ReservedResource.Organization, "<https://api.documind.com>"], // API resource identifier ที่สร้างใหม่
};
เช่นเคย ผู้ใช้ต้อง login ใหม่หลังอัปเดต Logto config
ใน Dashboard ดึง Logto Access Token ตอนสร้างองค์กร ใช้ token นี้เข้าถึง backend service API ของเรา
// ดึง access token สำหรับ "DocuMind API"
const token = await getAccessToken('<https://api.documind.com>');
// เข้าถึง backend service API ของเราด้วย token
const response = await fetch('<http://localhost:3000/organizations>', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
name: 'Organization A',
description: 'Organization A description',
}),
});
ตอนนี้เราสามารถเข้าถึง DocuMind backend service API ได้อย่างถูกต้อง
เรียก Logto Management API (Calling Logto Management API)
มาสร้างฟีเจอร์สร้างองค์กรด้วย Logto Management API
เหมือนกับ frontend ที่เรียก backend service backend service ก็ต้องใช้ Access token ในการเรียก Logto
ใน Logto เราใช้ Machine-to-Machine authentication เพื่อขอ access token ดู Interact with Management API
ไปที่หน้า applications ใน Logto Console สร้าง Machine-to-Machine application กำหนด role "Logto Management API access" คัดลอก Token endpoint, App ID, App Secret ไว้ใช้ขอ access token

ตอนนี้เราสามารถขอ Logto Management API access token ผ่าน M2M application นี้
async function fetchLogtoManagementApiAccessToken() {
const response = await fetch(process.env.LOGTO_MANAGEMENT_API_TOKEN_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(
`${process.env.LOGTO_MANAGEMENT_API_APPLICATION_ID}:${process.env.LOGTO_MANAGEMENT_API_APPLICATION_SECRET}`
).toString('base64')}`,
},
body: new URLSearchParams({
grant_type: 'client_credentials',
resource: process.env.LOGTO_MANAGEMENT_API_RESOURCE,
scope: 'all',
}).toString(),
});
const data = await response.json();
return data.access_token;
}
ใช้ access token นี้เรียก Logto Management API
เราจะใช้ Management API เหล่านี้:
POST /api/organizations: สร้างองค์กร (ดู: Create organization API reference)POST /api/organizations/{id}/users: เพิ่มผู้ใช้เข้าองค์กร (ดู: Add users to organization API reference)
app.post('/organizations', requireAuth('<https://api.documind.com>'), async (req, res) => {
const accessToken = await fetchLogtoManagementApiAccessToken();
// สร้างองค์กรใน Logto และเพิ่มผู้ใช้เข้าไป
const response = await fetch(`${process.env.LOGTO_ENDPOINT}/api/organizations`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
name: req.body.name,
description: req.body.description,
}),
});
const createdOrganization = await response.json();
await fetch(`${process.env.LOGTO_ENDPOINT}/api/organizations/${createdOrganization.id}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
userIds: [req.user.id],
}),
});
res.json({ data: createdOrganization });
});
ตอนนี้เราสร้างองค์กรผ่าน Logto Management API ได้แล้ว และเพิ่มผู้ใช้เข้าองค์กรได้ด้วย
มาทดสอบฟีเจอร์นี้ใน Dashboard

แล้วคลิก “Create Organization”

สร้างสำเร็จ!
ขั้นตอนถัดไปคือเชิญผู้ใช้เข้าองค์กร ฟีเจอร์นี้จะยังไม่ทำในคู่มือนี้ คุณรู้วิธีใช้ Management API แล้ว สามารถดู Tenant creation and invitation เป็นแนวทางออกแบบผลิตภัณฑ์ และทำตามบล็อกนี้เพื่อสร้างฟีเจอร์นี้ได้: How we implement user collaboration within a multi-tenant app
สร้างการควบคุมการเข้าถึงในแอปหลายผู้เช่า (Implement access control to your multi-tenant app)
ต่อไปมาดูการควบคุมการเข้าถึงขององค์กร
เป้าหมายคือ:
- ผู้ใช้เข้าถึงได้เฉพาะทรัพยากรขององค์กรตัวเอง: ทำได้ด้วย
โทเค็นองค์กร (Organization token) - ผู้ใช้มีบทบาทเฉพาะในแต่ละองค์กร (พร้อมสิทธิ์ต่างกัน) เพื่อดำเนินการที่ได้รับอนุญาต: ใช้ฟีเจอร์ organization template ของ Logto
มาดูวิธีใช้งานกัน
ใช้โทเค็นองค์กรของ Logto (Using Logto organization token)
คล้ายกับ access token ของ Logto ที่กล่าวไป Logto จะออก access token สำหรับ resource เฉพาะ และผู้ใช้ใช้ token นี้เข้าถึง resource ที่ปกป้องใน backend service ในทำนองเดียวกัน Logto จะออก organization token สำหรับองค์กรเฉพาะ และผู้ใช้ใช้ token นี้เข้าถึง resource ขององค์กรใน backend service
ใน frontend ใช้เมธอด getOrganizationToken ของ Logto เพื่อขอโทเค็นสำหรับเข้าถึงองค์กร
const { getOrganizationToken } = useLogto();
const organizationToken = await getOrganizationToken(organizationId);
organizationId คือ id ขององค์กรที่ผู้ใช้เป็นสมาชิก
ก่อนใช้ getOrganization หรือฟีเจอร์องค์กร ต้องแน่ใจว่า scope urn:logto:scope:organizations และ resource urn:logto:resource:organization ถูกประกาศใน Logto config ซึ่งเราได้ประกาศไว้แล้ว
ในหน้าองค์กร เราใช้ organization token เพื่อดึงเอกสารในองค์กร
function OrganizationPage() {
const { organizationId } = useParams();
const navigate = useNavigate();
const { signOut, getOrganizationToken } = useLogto();
const [error, setError] = useState<Error | null>(null);
const [documents, setDocuments] = useState([]);
const fetchDocuments = useCallback(async () => {
if (!organizationId) return;
try {
const organizationToken = await getOrganizationToken(organizationId);
const response = await fetch(`http://localhost:3000/documents`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${organizationToken}`,
},
});
const documents = await response.json();
setDocuments(documents);
} catch (error: unknown) {
if (error instanceof Error) {
setError(error);
} else {
setError(new Error(String(error)));
}
}
},[getOrganizationToken, organizationId]);
useEffect(() => {
void fetchDocuments();
}, [fetchDocuments]);
if (error) {
return <div>Error: {error.message}</div>;
}
return <div>
<h1>Organization Documents</h1>
<ul>
{documents.map((document) => (
<li key={document.id}>{document.name}</li>
))}
</ul>
</div>
}
จุดสำคัญสองข้อใน implementation นี้:
- ถ้า
organizationIdที่ส่งให้getOrganizationTokenไม่ใช่องค์กรที่ผู้ใช้เป็นสมาชิก จะไม่ได้ token เพื่อให้แน่ใจว่าผู้ใช้เข้าถึงได้เฉพาะองค์กรตัวเอง - เวลาขอ resource ขององค์กร เราใช้ organization token แทน access token เพราะ resource ที่เป็นขององค์กรต้องใช้การควบคุมสิทธิ์แบบองค์กร ไม่ใช่แบบผู้ใช้ (จะเข้าใจมากขึ้นเมื่อ implement API
GET /documents)
ต่อไป สร้าง API GET /documents ใน backend service คล้ายกับที่ใช้ API resource ปกป้อง POST /organizations API นี้จะใช้ resource indicator เฉพาะองค์กรปกป้อง
สร้าง middleware requireOrganizationAccess เพื่อปกป้อง resource ขององค์กร
const getTokenFromHeader = (headers) => {
const { authorization } = headers;
const bearerTokenIdentifier = 'Bearer';
if (!authorization) {
throw new Error('Authorization header missing');
}
if (!authorization.startsWith(bearerTokenIdentifier)) {
throw new Error('Authorization token type not supported');
}
return authorization.slice(bearerTokenIdentifier.length + 1);
};
const extractOrganizationId = (aud) => {
if (!aud || typeof aud !== 'string' || !aud.startsWith('urn:logto:organization:')) {
throw new Error('Invalid organization token');
}
return aud.replace('urn:logto:organization:', '');
};
const decodeJwtPayload = (token) => {
try {
const [, payloadBase64] = token.split('.');
if (!payloadBase64) {
throw new Error('Invalid token format');
}
const payloadJson = Buffer.from(payloadBase64, 'base64').toString('utf-8');
return JSON.parse(payloadJson);
} catch (error) {
throw new Error('Failed to decode token payload');
}
};
const requireOrganizationAccess = () => {
return async (req, res, next) => {
try {
// ดึง token
const token = getTokenFromHeader(req.headers);
// ดึง audience จาก token
const { aud } = decodeJwtPayload(token);
if (!aud) {
throw new Error('Missing audience in token');
}
// ตรวจสอบ token กับ audience
const { payload } = await jwtVerify(
token,
createRemoteJWKSet(new URL(process.env.LOGTO_JWKS_URL)),
{
issuer: process.env.LOGTO_ISSUER,
audience: aud,
}
);
// ดึง organization ID จาก audience claim
const organizationId = extractOrganizationId(payload.aud);
// เพิ่มข้อมูลองค์กรใน request
req.user = {
id: payload.sub,
organizationId,
};
next();
} catch (error) {
console.error('Organization auth error:', error);
res.status(401).json({ error: 'Unauthorized - Invalid organization access' });
}
};
};
ใช้ middleware requireOrganizationAccess ปกป้อง API GET /documents
app.get('/documents', requireOrganizationAccess(), async (req, res) => {
// ดึง user id และ organizationId จาก req.user
console.log('userId', req.user.id);
console.log('organizationId', req.user.organizationId);
// ดึงเอกสารจากฐานข้อมูลด้วย organizationId
// ....
const documents = await getDocumentsByOrganizationId(req.user.organizationId);
res.json(documents);
});
ด้วยวิธีนี้ เราใช้ organization token เข้าถึง resource ขององค์กรได้ ใน backend service สามารถดึง resource จากฐานข้อมูลตาม organization id
ซอฟต์แวร์บางตัวต้องการแยกข้อมูลระหว่างองค์กร ดูรายละเอียดและตัวอย่างเพิ่มเติมได้ที่: Multi-tenancy implementation with PostgreSQL: Learn through a simple real-world example
สร้าง RBAC ระดับองค์กร (Implement organization-level role-based access control design)
เราใช้ organization token เข้าถึง resource ขององค์กรแล้ว ต่อไปจะควบคุมสิทธิ์ผู้ใช้ในองค์กรด้วย RBAC
สมมติว่า DocuMind มีสองบทบาท: Admin และ Collaborator
Admin สร้างและเข้าถึงเอกสารได้ Collaborator เข้าถึงเอกสารได้อย่างเดียว
ดังนั้น องค์กรต้องมีสองบทบาทนี้: Admin และ Collaborator
Admin มีสิทธิ์ read:documents และ create:documents ส่วน Collaborator มีแค่ read:documents
- Admin
read:documentscreate:documents
- Collaborator
read:documents
ตรงนี้ใช้ฟีเจอร์ organization template ของ Logto
organization template คือแม่แบบโมเดลการควบคุมการเข้าถึงของแต่ละองค์กร: กำหนดบทบาทและสิทธิ์ที่ใช้กับทุกองค์กร
ทำไมต้องมี organization template?
เพราะความสามารถในการขยาย (scalability) คือข้อกำหนดสำคัญของ SaaS กล่าวคือ สิ่งที่ใช้ได้กับลูกค้าหนึ่งต้องใช้ได้กับทุกคน
ไปที่ Logto Console > Organization Templates > Organization permissions สร้าง permission สองตัว: read:documents และ create:documents

จากนั้นไปที่แท็บ organization roles สร้าง user role สองตัว: Admin และ Collaborator แล้วกำหนด permission ให้แต่ละ role

ตอนนี้เราสร้างโมเดล RBAC สำหรับแต่ละองค์กรแล้ว
ต่อไปไปที่หน้า Organization details เพื่อกำหนดบทบาทให้สมาชิก

ตอนนี้ผู้ใช้ในองค์กรมีบทบาทแล้ว! คุณสามารถทำขั้นตอนนี้ผ่าน Logto Management API ได้เช่นกัน:
// กำหนด role 'Admin' ให้ผู้สร้างองค์กร
app.post('/organizations', requireAuth('https://api.documind.com'), async (req, res) => {
const accessToken = await fetchLogtoManagementApiAccessToken();
// สร้างองค์กรใน Logto
// โค้ดเดิม...
// เพิ่มผู้ใช้เข้าองค์กรใน Logto
await fetch(`${process.env.LOGTO_ENDPOINT}/api/organizations/${createdOrganization.id}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
userIds: [req.user.id],
}),
});
// กำหนด role `Admin` ให้ผู้ใช้คนแรก
const rolesResponse = await fetch(`${process.env.LOGTO_ENDPOINT}/api/organization-roles`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
});
const roles = await rolesResponse.json();
// หา role `Admin`
const adminRole = roles.find((role) => role.name === 'Admin');
// กำหนด role `Admin` ให้ผู้ใช้คนแรก
await fetch(
`${process.env.LOGTO_ENDPOINT}/api/organizations/${createdOrganization.id}/users/${req.user.id}/roles`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
organizationRoleIds: [adminRole.id],
}),
}
);
// โค้ดเดิม...
});
ตอนนี้เราควบคุมสิทธิ์ผู้ใช้ได้โดยตรวจสอบ permission ของเขา
ในโค้ด เราต้องให้ organization token ของผู้ใช้มีข้อมูล permission แล้วตรวจสอบ permission เหล่านี้ใน backend
ใน Logto config ของ frontend ให้ประกาศ permission ที่ผู้ใช้ต้องขอในองค์กร เช่น read:documents และ create:documents ใน scopes
const config: LogtoConfig = {
endpoint: "<YOUR_LOGTO_ENDPOINT>",
appId: "<YOUR_LOGTO_APP_ID>",
scopes: [UserScope.Organizations, "read:documents", "create:documents"],
resources: [ReservedResource.Organization, "<https://api.documind.com>"], // API resource identifier ที่สร้างใหม่
};
เช่นเคย login ใหม่เพื่อให้ config นี้มีผล
จากนั้นใน middleware requireOrganizationAccess ของ backend เพิ่มการตรวจสอบ permission ของผู้ใช้
const hasRequiredScopes = (tokenScopes, requiredScopes) => {
if (!requiredScopes || requiredScopes.length === 0) {
return true;
}
const scopeSet = new Set(tokenScopes);
return requiredScopes.every((scope) => scopeSet.has(scope));
};
const requireOrganizationAccess = ({ requiredScopes = [] } = {}) => {
return async (req, res, next) => {
try {
//...
// ตรวจสอบ token กับ audience
const { payload } = await jwtVerify(
token,
createRemoteJWKSet(new URL(process.env.LOGTO_JWKS_URL)),
{
issuer: process.env.LOGTO_ISSUER,
audience: aud,
}
);
//...
// ดึง scopes จาก token
const scopes = payload.scope?.split(' ') || [];
// ตรวจสอบ required scopes
if (!hasRequiredScopes(scopes, requiredScopes)) {
throw new Error('Insufficient permissions');
}
//...
next();
} catch (error) {
//...
}
};
};
จากนั้นสร้าง API POST /documents และใช้ middleware requireOrganizationAccess พร้อม requiredScopes เพื่อปกป้อง API นี้และ API GET /documents ก่อนหน้า
// API สำหรับสร้างเอกสาร
app.post(
'/documents',
requireOrganizationAccess({ requiredScopes: ['create:documents'] }),
async (req, res) => {
//...
}
);
// API สำหรับดึงเอกสาร
app.get(
'/documents',
requireOrganizationAccess({ requiredScopes: ['read:documents'] }),
async (req, res) => {
//...
}
);
ด้วยวิธีนี้ เราควบคุมสิทธิ์ผู้ใช้โดยตรวจสอบ permission ของเขา
ใน frontend สามารถดึงข้อมูล permission ของผู้ใช้ได้โดย decode organization token หรือเรียกเมธอด getOrganizationTokenClaims ของ Logto
const [scopes, setScopes] = useState([]);
const { getOrganizationTokenClaims } = useLogto();
const loadScopes = async () => {
const claims = await getOrganizationTokenClaims(organizationId);
setScopes(claims.scope.split(' '));
};
// ...
ควบคุม element ในหน้าเว็บตาม permission ของผู้ใช้โดยตรวจสอบ scopes ใน claims
เพิ่มฟีเจอร์แอปหลายผู้เช่าเพิ่มเติม (Add more multi-tenant app features)
จนถึงตอนนี้ เราสร้างฟีเจอร์ผู้ใช้และองค์กรพื้นฐานในระบบ SaaS หลายผู้เช่าแล้ว! แต่ยังมีฟีเจอร์อื่น ๆ ที่ยังไม่ได้กล่าวถึง เช่น การปรับแต่งแบรนด์หน้า login สำหรับแต่ละองค์กร การเพิ่มผู้ใช้ที่มีอีเมลโดเมนเฉพาะเข้าองค์กรอัตโนมัติ และการเชื่อมต่อ SSO ระดับองค์กร
ฟีเจอร์เหล่านี้มีให้ใช้ทันที และดูรายละเอียดเพิ่มเติมได้ในเอกสาร Logto:
- การเชื่อมต่อ Enterprise SSO
- Just-in-Time (JIT) Provisioning
- การปรับแต่งแบรนด์ระดับองค์กร
- MFA ระดับองค์กร
- การจัดการระดับองค์กร
สรุป (Summary)
จำได้ไหมว่าตอนแรกมันดูน่ากลัวแค่ไหน? ผู้ใช้ องค์กร สิทธิ์ ฟีเจอร์องค์กร... เหมือนภูเขาที่ไม่มีวันปีนถึงยอด
แต่มาดูสิ่งที่เราทำได้:
- ระบบการยืนยันตัวตน (Authentication) ครบถ้วน พร้อมตัวเลือกลงชื่อเข้าใช้หลายแบบและรองรับ MFA
- ระบบองค์กรที่ยืดหยุ่น รองรับสมาชิกหลายองค์กร
- การควบคุมการเข้าถึงตามบทบาท (RBAC) ภายในองค์กร
และที่ดีที่สุด? เราไม่ต้องสร้างวงล้อใหม่เอง ด้วยเครื่องมือสมัยใหม่อย่าง Logto เราเปลี่ยนงานที่อาจใช้เวลาหลายเดือนให้เหลือแค่ไม่กี่นาที
ซอร์สโค้ดทั้งหมดของคู่มือนี้อยู่ที่: Multi-tenant SaaS Sample
นี่คือพลังของการพัฒนาแบบสมัยใหม่ในปี 2025 — เราสามารถโฟกัสกับฟีเจอร์เฉพาะของผลิตภัณฑ์ แทนที่จะเสียเวลากับโครงสร้างพื้นฐาน ถึงตาคุณแล้วที่จะสร้างสิ่งที่น่าทึ่ง!
สำรวจฟีเจอร์ทั้งหมดของ Logto ทั้ง Logto Cloud และ Logto OSS ได้ที่ เว็บไซต์ Logto หรือสมัคร Logto cloud วันนี้