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

การสวมรอยผู้ใช้ (User impersonation)

ลองจินตนาการว่า Sarah วิศวกรฝ่ายสนับสนุนของ TechCorp ได้รับทิกเก็ตด่วนจาก Alex ลูกค้าที่ไม่สามารถเข้าถึงทรัพยากรสำคัญได้ เพื่อวินิจฉัยและแก้ไขปัญหาอย่างมีประสิทธิภาพ Sarah จำเป็นต้องเห็นสิ่งเดียวกับที่ Alex เห็นในระบบ นี่คือจุดที่ฟีเจอร์การสวมรอยผู้ใช้ของ Logto มีประโยชน์อย่างยิ่ง

การสวมรอยผู้ใช้ช่วยให้ผู้ใช้ที่ได้รับอนุญาต เช่น Sarah สามารถดำเนินการแทนผู้ใช้อื่น เช่น Alex ได้ชั่วคราวภายในระบบ ฟีเจอร์นี้ทรงพลังมากสำหรับการแก้ไขปัญหา การให้บริการลูกค้า และการดำเนินงานในฐานะผู้ดูแลระบบ

ทำงานอย่างไร?

กระบวนการสวมรอยประกอบด้วย 3 ขั้นตอนหลัก:

  1. Sarah ขอสิทธิ์สวมรอยผ่าน backend server ของ TechCorp
  2. เซิร์ฟเวอร์ของ TechCorp ขอ subject token จาก Logto Management API
  3. แอปของ Sarah แลก subject token นี้เป็นโทเค็นการเข้าถึง (access token)

มาดูกันว่า Sarah จะใช้ฟีเจอร์นี้เพื่อช่วยเหลือ Alex ได้อย่างไร

ขั้นตอนที่ 1: ขอสิทธิ์สวมรอย

ก่อนอื่น แอปสนับสนุนของ Sarah ต้องขอสิทธิ์สวมรอยจาก backend server ของ TechCorp

คำขอ (แอปของ Sarah ไปยังเซิร์ฟเวอร์ของ TechCorp)

POST /api/request-impersonation HTTP/1.1
Host: api.techcorp.com
Authorization: Bearer <Sarah's_access_token>
Content-Type: application/json

{
"userId": "alex123",
"reason": "Investigating resource access issue",
"ticketId": "TECH-1234"
}

ใน API นี้ backend ควรตรวจสอบการอนุญาตอย่างเหมาะสมเพื่อให้แน่ใจว่า Sarah มีสิทธิ์ที่จำเป็นในการสวมรอย Alex

ขั้นตอนที่ 2: ขอ subject token

เมื่อเซิร์ฟเวอร์ของ TechCorp ตรวจสอบคำขอของ Sarah แล้ว จะเรียก Management API ของ Logto เพื่อขอ subject token

คำขอ (เซิร์ฟเวอร์ของ TechCorp ไปยัง Logto Management API)

POST /api/subject-tokens HTTP/1.1
Host: techcorp.logto.app
Authorization: Bearer <TechCorp_m2m_access_token>
Content-Type: application/json

{
"userId": "alex123",
"context": {
"ticketId": "TECH-1234",
"reason": "Resource access issue",
"supportEngineerId": "sarah789"
}
}

การตอบกลับ (Logto ไปยังเซิร์ฟเวอร์ของ TechCorp)

{
"subjectToken": "sub_7h32jf8sK3j2",
"expiresIn": 600
}

เซิร์ฟเวอร์ของ TechCorp ควรส่ง subject token นี้กลับไปยังแอปของ Sarah

การตอบกลับ (เซิร์ฟเวอร์ของ TechCorp ไปยังแอปของ Sarah)

{
"subjectToken": "sub_7h32jf8sK3j2",
"expiresIn": 600
}

ขั้นตอนที่ 3: แลก subject token เป็น access token

ข้อกำหนดเบื้องต้น:

ก่อนใช้งาน token exchange grant คุณต้องเปิดใช้งานสำหรับแอปพลิเคชันของคุณก่อน:

  1. ไปที่ Console > Applications และเลือกแอปพลิเคชันของคุณ
  2. ในหน้าการตั้งค่าแอปพลิเคชัน ให้ค้นหาส่วน "Token exchange"
  3. เปิดสวิตช์ "Allow token exchange"

Token exchange ถูกปิดใช้งานโดยค่าเริ่มต้นเพื่อเหตุผลด้านความปลอดภัย หากคุณไม่เปิดใช้งาน คุณจะได้รับข้อผิดพลาดว่า "token exchange is not allowed for this application"

ตอนนี้แอปของ Sarah จะแลก subject token นี้เป็นโทเค็นการเข้าถึง (access token) ที่แสดงตัวตนของ Alex โดยระบุทรัพยากรที่โทเค็นจะถูกใช้

คำขอ (แอปของ Sarah ไปยัง token endpoint ของ Logto)

สำหรับเว็บแอปแบบดั้งเดิมหรือแอปเครื่องต่อเครื่องที่มี app secret ให้ใส่ข้อมูลรับรองใน Authorization header:

POST /oidc/token HTTP/1.1
Host: techcorp.logto.app
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&scope=resource:read
&subject_token=alx_7h32jf8sK3j2
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api.techcorp.com/customer-data

สำหรับ single-page application (SPA) หรือ native application ที่ไม่มี app secret ให้ใส่ client_id ใน request body:

POST /oidc/token HTTP/1.1
Host: techcorp.logto.app
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&client_id=techcorp_support_app
&scope=resource:read
&subject_token=alx_7h32jf8sK3j2
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api.techcorp.com/customer-data

การตอบกลับ (Logto ไปยังแอปของ Sarah)

{
"access_token": "eyJhbG...<truncated>",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "resource:read"
}

access_token ที่ได้รับจะถูกผูกกับทรัพยากรที่ระบุไว้ ทำให้สามารถใช้ได้เฉพาะกับ API ข้อมูลลูกค้าของ TechCorp เท่านั้น

ตัวอย่างการใช้งาน

นี่คือตัวอย่างที่ Sarah อาจใช้ในแอป Node.js ฝ่ายสนับสนุน:

interface ImpersonationResponse {
subjectToken: string;
expiresIn: number;
}

interface TokenExchangeResponse {
access_token: string;
issued_token_type: string;
token_type: string;
expires_in: number;
scope: string;
}

async function impersonateUser(
userId: string,
clientId: string,
ticketId: string,
resource: string,
clientSecret?: string // จำเป็นสำหรับเว็บแอปแบบดั้งเดิมหรือแอปเครื่องต่อเครื่อง
): Promise<string> {
try {
// ขั้นตอนที่ 1 & 2: ขอสิทธิ์สวมรอยและรับ subject token
const impersonationResponse = await fetch(
'https://api.techcorp.com/api/request-impersonation',
{
method: 'POST',
headers: {
Authorization: "Bearer <Sarah's_access_token>",
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId,
reason: 'Investigating resource access issue',
ticketId,
}),
}
);

if (!impersonationResponse.ok) {
throw new Error(`เกิดข้อผิดพลาด HTTP. สถานะ: ${impersonationResponse.status}`);
}

const { subjectToken } = (await impersonationResponse.json()) as ImpersonationResponse;

// ขั้นตอนที่ 3: แลก subject token เป็น access token
// สำหรับเว็บแอปแบบดั้งเดิมหรือแอป M2M ใช้ Basic auth พร้อม client secret
// สำหรับ SPA หรือ native app ให้ใส่ client_id ใน body
const headers: Record<string, string> = {
'Content-Type': 'application/x-www-form-urlencoded',
};

const tokenExchangeBody = new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
scope: 'openid profile resource.read',
subject_token: subjectToken,
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
resource: resource,
});

if (clientSecret) {
// Confidential client: ใช้ Basic auth
headers['Authorization'] =
`Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
} else {
// Public client: ใส่ client_id ใน body
tokenExchangeBody.append('client_id', clientId);
}

const tokenExchangeResponse = await fetch('https://techcorp.logto.app/oidc/token', {
method: 'POST',
headers,
body: tokenExchangeBody,
});

if (!tokenExchangeResponse.ok) {
throw new Error(`เกิดข้อผิดพลาด HTTP! สถานะ: ${tokenExchangeResponse.status}`);
}

const tokenData = (await tokenExchangeResponse.json()) as TokenExchangeResponse;
return tokenData.access_token;
} catch (error) {
console.error('การสวมรอยล้มเหลว:', error);
throw error;
}
}

// Sarah ใช้ฟังก์ชันนี้เพื่อสวมรอย Alex
async function performImpersonation(): Promise<void> {
try {
// สำหรับเว็บแอปแบบดั้งเดิมหรือแอป M2M ให้ส่ง client secret
const accessToken = await impersonateUser(
'alex123',
'techcorp_support_app',
'TECH-1234',
'https://api.techcorp.com/customer-data',
'your-client-secret' // ไม่ต้องใส่สำหรับ SPA หรือ native app
);
console.log('โทเค็นการเข้าถึงสำหรับการสวมรอย Alex:', accessToken);
} catch (error) {
console.error('สวมรอยไม่สำเร็จ:', error);
}
}

// เรียกใช้งานการสวมรอย
void performImpersonation();
บันทึก:
  1. subject token มีอายุสั้นและใช้ได้เพียงครั้งเดียว
  2. โทเค็นการเข้าถึงสำหรับการสวมรอยจะไม่มี refresh token หากหมดอายุ Sarah ต้องทำกระบวนการนี้ใหม่อีกครั้ง
  3. backend server ของ TechCorp ต้องตรวจสอบการอนุญาตอย่างเหมาะสมเพื่อให้แน่ใจว่าเฉพาะเจ้าหน้าที่สนับสนุนที่ได้รับอนุญาต เช่น Sarah เท่านั้นที่สามารถขอสวมรอยได้

act claim

เมื่อใช้ token exchange flow สำหรับการสวมรอย access token ที่ออกให้สามารถมี act (actor) claim เพิ่มเติมได้ claim นี้แสดงตัวตนของ “ผู้ดำเนินการ” — ในตัวอย่างนี้คือ Sarah ผู้ที่กำลังสวมรอย

หากต้องการให้มี act claim แอปของ Sarah ต้องส่ง actor_token ในคำขอ token exchange โดย token นี้ควรเป็น access token ที่ถูกต้องของ Sarah พร้อมขอบเขต openid ตัวอย่างการใส่ในคำขอ:

สำหรับเว็บแอปแบบดั้งเดิมหรือแอปเครื่องต่อเครื่อง:

POST /oidc/token HTTP/1.1
Host: techcorp.logto.app
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&scope=resource:read
&subject_token=alx_7h32jf8sK3j2
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&actor_token=sarah_access_token
&actor_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api.techcorp.com/customer-data

สำหรับ SPA หรือ native app ให้ใส่ client_id ใน body แทน:

POST /oidc/token HTTP/1.1
Host: techcorp.logto.app
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&client_id=techcorp_support_app
&scope=resource:read
&subject_token=alx_7h32jf8sK3j2
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&actor_token=sarah_access_token
&actor_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api.techcorp.com/customer-data

หากมีการส่ง actor_token โทเค็นการเข้าถึงที่ได้จะมี act claim เช่นนี้:

{
"aud": "https://api.techcorp.com",
"iss": "https://techcorp.logto.app",
"exp": 1443904177,
"sub": "alex123",
"act": {
"sub": "sarah789"
}
}

act claim นี้แสดงอย่างชัดเจนว่า Sarah (sarah789) กำลังดำเนินการแทน Alex (alex123) ซึ่งเป็นประโยชน์สำหรับการตรวจสอบและติดตามการสวมรอย

การปรับแต่ง token claims

Logto อนุญาตให้คุณ ปรับแต่ง token claims สำหรับโทเค็นการสวมรอย ซึ่งมีประโยชน์สำหรับการเพิ่มข้อมูลบริบทหรือเมตาดาต้าเพิ่มเติม เช่น เหตุผลในการสวมรอยหรือหมายเลขทิกเก็ตที่เกี่ยวข้อง

เมื่อเซิร์ฟเวอร์ของ TechCorp ขอ subject token จาก Logto Management API สามารถใส่ context object ได้ดังนี้:

{
"userId": "alex123",
"context": {
"ticketId": "TECH-1234",
"reason": "Resource access issue",
"supportEngineerId": "sarah789"
}
}

context นี้สามารถนำไปใช้ในฟังก์ชัน getCustomJwtClaims() เพื่อเพิ่ม claim เฉพาะลงใน access token สุดท้าย ตัวอย่างเช่น:

const getCustomJwtClaims = async ({ token, context, environmentVariables }) => {
if (context.grant?.type === 'urn:ietf:params:oauth:grant-type:token-exchange') {
const { ticketId, reason, supportEngineerId } = context.grant.subjectTokenContext;
return {
impersonation_context: {
ticket_id: ticketId,
reason: reason,
support_engineer: supportEngineerId,
},
};
}
return {};
};

access token ที่ Sarah ได้รับอาจมีลักษณะดังนี้:

{
"sub": "alex123",
"aud": "https://api.techcorp.com/customer-data",
"impersonation_context": {
"ticket_id": "TECH-1234",
"reason": "Resource access issue",
"support_engineer": "sarah789"
}
// ... claim มาตรฐานอื่น ๆ
}

ด้วยการปรับแต่ง access token claims แบบนี้ TechCorp สามารถใส่ข้อมูลสำคัญเกี่ยวกับบริบทการสวมรอย ช่วยให้ตรวจสอบและเข้าใจการดำเนินการสวมรอยในระบบได้ง่ายขึ้น

บันทึก:

โปรดระวังเมื่อเพิ่ม custom claim ลงในโทเค็นของคุณ หลีกเลี่ยงการใส่ข้อมูลสำคัญที่อาจก่อให้เกิดความเสี่ยงด้านความปลอดภัยหากโทเค็นถูกดักจับหรือรั่วไหล JWT จะถูกเซ็นชื่อแต่ไม่ถูกเข้ารหัส ดังนั้น claim จะมองเห็นได้สำหรับผู้ที่เข้าถึงโทเค็น

การสวมรอยในโลกไซเบอร์และการจัดการเอกลักษณ์คืออะไร? AI agent ใช้ประโยชน์จากมันได้อย่างไร?