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

เพิ่มการยืนยันตัวตนให้กับแอป Next.js (App Router) ของคุณ (Add authentication to your Next.js (App Router) application)

เคล็ดลับ:

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

การติดตั้ง

ติดตั้ง Logto SDK ผ่านตัวจัดการแพ็กเกจที่คุณชื่นชอบ:

npm i @logto/next

การเชื่อมต่อระบบ

เตรียมค่าคอนฟิก

เตรียมค่าคอนฟิกสำหรับ Logto client:

app/logto.ts
import { LogtoNextConfig } from '@logto/next';

export const logtoConfig: LogtoNextConfig = {
appId: '<your-application-id>',
appSecret: '<your-app-secret-copied-from-console>',
endpoint: '<your-logto-endpoint>', // เช่น http://localhost:3001
baseUrl: '<your-nextjs-app-base-url>', // เช่น http://localhost:3000
cookieSecret: 'complex_password_at_least_32_characters_long',
cookieSecure: process.env.NODE_ENV === 'production',
};

หมายเหตุ:
หากคุณใช้ environment variable สำหรับ cookieSecret (เช่น process.env.LOGTO_COOKIE_SECRET) ให้แน่ใจว่าค่ามีความยาวอย่างน้อย 32 ตัวอักษร หากไม่เป็นไปตามนี้ Logto จะเกิดข้อผิดพลาดต่อไปนี้ระหว่าง build หรือ runtime:

TypeError: Either sessionWrapper or encryptionKey must be provided for CookieStorage

เพื่อป้องกันข้อผิดพลาดนี้ ให้ตรวจสอบว่า environment variable ถูกตั้งค่าอย่างถูกต้อง หรือกำหนดค่า fallback ที่มีความยาวอย่างน้อย 32 ตัวอักษร

กำหนดค่า redirect URI

ก่อนที่เราจะลงลึกในรายละเอียด นี่คือภาพรวมประสบการณ์ของผู้ใช้ปลายทาง กระบวนการลงชื่อเข้าใช้สามารถสรุปได้ดังนี้:

  1. แอปของคุณเรียกใช้งานเมธอดลงชื่อเข้าใช้
  2. ผู้ใช้จะถูกเปลี่ยนเส้นทางไปยังหน้าลงชื่อเข้าใช้ของ Logto สำหรับแอปเนทีฟ ระบบจะเปิดเบราว์เซอร์ของระบบ
  3. ผู้ใช้ลงชื่อเข้าใช้และถูกเปลี่ยนเส้นทางกลับไปยังแอปของคุณ (ตามที่กำหนดไว้ใน redirect URI)

เกี่ยวกับการลงชื่อเข้าใช้แบบเปลี่ยนเส้นทาง (redirect-based sign-in)

  1. กระบวนการยืนยันตัวตนนี้เป็นไปตามโปรโตคอล OpenID Connect (OIDC) และ Logto บังคับใช้มาตรการรักษาความปลอดภัยอย่างเข้มงวดเพื่อปกป้องการลงชื่อเข้าใช้ของผู้ใช้
  2. หากคุณมีหลายแอป คุณสามารถใช้ผู้ให้บริการข้อมูลระบุตัวตน (Logto) เดียวกันได้ เมื่อผู้ใช้ลงชื่อเข้าใช้แอปหนึ่งแล้ว Logto จะดำเนินการลงชื่อเข้าใช้โดยอัตโนมัติเมื่อผู้ใช้เข้าถึงแอปอื่น

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


บันทึก:

ในตัวอย่างโค้ดต่อไปนี้ เราถือว่าแอปของคุณกำลังทำงานอยู่ที่ http://localhost:3000/

กำหนดค่า Redirect URI

ไปที่หน้ารายละเอียดแอปพลิเคชันใน Logto Console เพิ่ม redirect URI http://localhost:3000/callback

Redirect URI in Logto Console

เช่นเดียวกับการลงชื่อเข้าใช้ ผู้ใช้ควรถูกเปลี่ยนเส้นทางไปที่ Logto เพื่อออกจากเซสชันที่ใช้ร่วมกัน เมื่อเสร็จสิ้นแล้ว ควรเปลี่ยนเส้นทางผู้ใช้กลับไปยังเว็บไซต์ของคุณ ตัวอย่างเช่น เพิ่ม http://localhost:3000/ ในส่วน post sign-out redirect URI

จากนั้นคลิก "Save" เพื่อบันทึกการเปลี่ยนแปลง

จัดการ callback

หลังจากผู้ใช้ลงชื่อเข้าใช้ Logto จะเปลี่ยนเส้นทางผู้ใช้กลับไปยัง redirect URI ที่กำหนดไว้ข้างต้น อย่างไรก็ตาม ยังมีสิ่งที่ต้องทำเพื่อให้แอปของคุณทำงานได้อย่างถูกต้อง

เรามีฟังก์ชันช่วยเหลือ handleSignIn สำหรับจัดการ callback หลังลงชื่อเข้าใช้:

app/callback/route.ts
import { handleSignIn } from '@logto/next/server-actions';
import { redirect } from 'next/navigation';
import { NextRequest } from 'next/server';
import { logtoConfig } from '../logto';

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
await handleSignIn(logtoConfig, searchParams);

redirect('/');
}

สร้างปุ่มลงชื่อเข้าใช้และออกจากระบบ

สร้างปุ่มลงชื่อเข้าใช้และออกจากระบบ

ใน Next.js App Router เหตุการณ์จะถูกจัดการใน client component ดังนั้นเราต้องสร้าง component สองตัวก่อน: SignIn และ SignOut

app/sign-in.tsx
'use client';

type Props = {
onSignIn: () => Promise<void>;
};

const SignIn = ({ onSignIn }: Props) => {
return (
<button
onClick={() => {
onSignIn();
}}
>
Sign In
</button>
);
};

export default SignIn;
app/sign-out.tsx
'use client';

type Props = {
onSignOut: () => Promise<void>;
};

const SignOut = ({ onSignOut }: Props) => {
return (
<button
onClick={() => {
onSignOut();
}}
>
Sign Out
</button>
);
};

export default SignOut;

อย่าลืมเพิ่ม 'use client' ไว้ที่ด้านบนของไฟล์เพื่อระบุว่า component เหล่านี้เป็น client component

เพิ่มปุ่มลงชื่อเข้าใช้และออกจากระบบในหน้าแรก

บันทึก:

ไม่อนุญาตให้กำหนด Server Actions ที่มี annotation "use server" แบบอินไลน์ใน Client Components เราต้องส่งผ่านผ่าน props จาก Server Component เท่านั้น

ตอนนี้มาเพิ่มปุ่มลงชื่อเข้าใช้และออกจากระบบในหน้าแรกของคุณ โดยต้องเรียก server actions ใน SDK เมื่อจำเป็น เพื่อช่วยในเรื่องนี้ ให้ใช้ getLogtoContext เพื่อดึงสถานะการยืนยันตัวตน

app/page.tsx
import { getLogtoContext, signIn, signOut } from '@logto/next/server-actions';
import SignIn from './sign-in';
import SignOut from './sign-out';
import { logtoConfig } from './logto';

const Home = () => {
const { isAuthenticated, claims } = await getLogtoContext(logtoConfig);

return (
<nav>
{isAuthenticated ? (
<p>
สวัสดี, {claims?.sub},
<SignOut
onSignOut={async () => {
'use server';

await signOut(logtoConfig);
}}
/>
</p>
) : (
<p>
<SignIn
onSignIn={async () => {
'use server';

await signIn(logtoConfig);
}}
/>
</p>
)}
</nav>
);
};

export default Home;

จุดตรวจสอบ: ทดสอบแอปพลิเคชันของคุณ

ตอนนี้คุณสามารถทดสอบแอปพลิเคชันของคุณได้แล้ว:

  1. รันแอปพลิเคชันของคุณ คุณจะเห็นปุ่มลงชื่อเข้าใช้
  2. คลิกปุ่มลงชื่อเข้าใช้ SDK จะเริ่มกระบวนการลงชื่อเข้าใช้และเปลี่ยนเส้นทางคุณไปยังหน้าลงชื่อเข้าใช้ของ Logto
  3. หลังจากที่คุณลงชื่อเข้าใช้แล้ว คุณจะถูกเปลี่ยนเส้นทางกลับไปยังแอปพลิเคชันของคุณและเห็นปุ่มลงชื่อออก
  4. คลิกปุ่มลงชื่อออกเพื่อเคลียร์ที่เก็บโทเค็นและออกจากระบบ

ดึงข้อมูลผู้ใช้

แสดงข้อมูลผู้ใช้

เมื่อผู้ใช้ลงชื่อเข้าใช้แล้ว ค่าที่ได้จาก getLogtoContext() จะเป็นอ็อบเจกต์ที่มีข้อมูลของผู้ใช้ คุณสามารถแสดงข้อมูลนี้ในแอปของคุณได้:

app/page.tsx
import { getLogtoContext } from '@logto/next/server-actions';
import { logtoConfig } from './logto';

// ฟังก์ชันหลักของหน้า Home
export default async function Home() {
const { claims } = await getLogtoContext(logtoConfig);

return (
<main>
{claims && (
<div>
<h2>การอ้างสิทธิ์ (Claims):</h2>
<table>
<thead>
<tr>
<th>ชื่อ</th>
<th>ค่า</th>
</tr>
</thead>
<tbody>
{Object.entries(claims).map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{String(value)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</main>
);
}

รับข้อมูลผู้ใช้ใน API route handlers

คุณยังสามารถรับข้อมูลผู้ใช้ใน route handlers ของ API ได้เช่นกัน:

app/api/profile/route.ts
import { getLogtoContext } from '@logto/next/server-actions';
import { logtoConfig } from '../../logto';

export const dynamic = 'force-dynamic';

export async function GET() {
const { claims } = await getLogtoContext(logtoConfig);

return Response.json({ claims });
}

ขอการอ้างสิทธิ์เพิ่มเติม

คุณอาจพบว่าข้อมูลผู้ใช้บางอย่างหายไปในอ็อบเจกต์ที่ส่งคืนจาก getLogtoContext สาเหตุเนื่องจาก OAuth 2.0 และ OpenID Connect (OIDC) ถูกออกแบบมาให้สอดคล้องกับหลักการสิทธิ์น้อยที่สุด (principle of least privilege; PoLP) และ Logto ถูกสร้างขึ้นบนมาตรฐานเหล่านี้

โดยปกติแล้ว จะมีการส่งคืนการอ้างสิทธิ์ (claim) แบบจำกัด หากคุณต้องการข้อมูลเพิ่มเติม คุณสามารถร้องขอขอบเขต (scope) เพิ่มเติมเพื่อเข้าถึงการอ้างสิทธิ์ (claim) ที่มากขึ้นได้

ข้อมูล:

"การอ้างสิทธิ์ (Claim)" คือการยืนยันข้อมูลบางอย่างเกี่ยวกับผู้ถูกอ้างถึง (subject); "ขอบเขต (Scope)" คือกลุ่มของการอ้างสิทธิ์ (claim) ในกรณีนี้ การอ้างสิทธิ์ (claim) คือข้อมูลบางอย่างเกี่ยวกับผู้ใช้

ตัวอย่างที่ไม่เป็นทางการของความสัมพันธ์ระหว่างขอบเขต (scope) กับการอ้างสิทธิ์ (claim) มีดังนี้:

เคล็ดลับ:

การอ้างสิทธิ์ (claim) "sub" หมายถึง "ผู้ถูกอ้างถึง (subject)" ซึ่งคือตัวระบุที่ไม่ซ้ำของผู้ใช้ (เช่น user ID)

Logto SDK จะร้องขอขอบเขต (scope) สามรายการเสมอ ได้แก่ openid, profile และ offline_access

หากต้องการขอขอบเขต (scopes) เพิ่มเติม คุณสามารถกำหนดค่า params ขณะ init Logto client ได้ดังนี้:

app/logto.ts
import { UserScope, LogtoNextConfig } from '@logto/next';

export const logtoConfig: LogtoNextConfig = {
scopes: [UserScope.Email, UserScope.Phone], // เพิ่ม scopes ตามต้องการ
// ...other configs
});

จากนั้นคุณสามารถเข้าถึงการอ้างสิทธิ์เพิ่มเติมใน context response ได้:

app/page.tsx
export default async function Home() {
const { claims: { email } = {}, } = await getLogtoContext(logtoConfig);

return (
<div>
{email && <p>อีเมล: {email}</p>}
</div>
);
};

export default Home;

การอ้างสิทธิ์ (Claims) ที่ต้องใช้การร้องขอผ่านเครือข่าย

เพื่อป้องกันไม่ให้โทเค็น ID (ID token) มีขนาดใหญ่เกินไป การอ้างสิทธิ์บางรายการจำเป็นต้องร้องขอผ่านเครือข่ายเพื่อดึงข้อมูล ตัวอย่างเช่น การอ้างสิทธิ์ custom_data จะไม่ถูกรวมอยู่ในอ็อบเจกต์ผู้ใช้ แม้ว่าจะร้องขอไว้ในขอบเขต (scopes) ก็ตาม หากต้องการเข้าถึงการอ้างสิทธิ์เหล่านี้ คุณสามารถกำหนดค่าออปชัน fetchUserInfo:

app/page.tsx
export default async function Home() {
const { userInfo } = await getLogtoContext(logtoConfig, { fetchUserInfo: true });
return (
<div>
{userInfo && <p>อีเมล: {userInfo.email}</p>}
</div>
);
};

export default Home;
เมื่อกำหนดค่า fetchUserInfo SDK จะดึงข้อมูลผู้ใช้โดยการร้องขอไปยัง จุดปลาย userinfo หลังจากที่ผู้ใช้ลงชื่อเข้าใช้แล้ว และ userInfo จะพร้อมใช้งานเมื่อการร้องขอเสร็จสมบูรณ์

ขอบเขตและการอ้างสิทธิ์

Logto ใช้มาตรฐาน ขอบเขต (scopes) และ การอ้างสิทธิ์ (claims) ของ OIDC เพื่อกำหนดขอบเขตและการอ้างสิทธิ์สำหรับดึงข้อมูลผู้ใช้จากโทเค็น ID (ID token) และ OIDC userinfo endpoint ทั้ง "ขอบเขต (scope)" และ "การอ้างสิทธิ์ (claim)" เป็นคำศัพท์จากข้อกำหนดของ OAuth 2.0 และ OpenID Connect (OIDC)

ต่อไปนี้คือรายการขอบเขต (Scopes) ที่รองรับและการอ้างสิทธิ์ (Claims) ที่เกี่ยวข้อง:

openid

ชื่อการอ้างสิทธิ์ประเภทคำอธิบายต้องใช้ userinfo หรือไม่?
substringตัวระบุที่ไม่ซ้ำของผู้ใช้ไม่

profile

ชื่อการอ้างสิทธิ์ประเภทคำอธิบายต้องใช้ userinfo หรือไม่?
namestringชื่อเต็มของผู้ใช้ไม่
usernamestringชื่อผู้ใช้ของผู้ใช้ไม่
picturestringURL ของรูปโปรไฟล์ของผู้ใช้ปลายทาง URL นี้ ต้อง อ้างอิงถึงไฟล์รูปภาพ (เช่น ไฟล์ PNG, JPEG หรือ GIF) ไม่ใช่หน้าเว็บที่มีรูปภาพ โปรดทราบว่า URL นี้ ควร อ้างอิงถึงรูปโปรไฟล์ของผู้ใช้ปลายทางที่เหมาะสมสำหรับการแสดงผลเมื่ออธิบายผู้ใช้ปลายทาง ไม่ใช่รูปภาพใด ๆ ที่ผู้ใช้ถ่ายเองไม่
created_atnumberเวลาที่สร้างผู้ใช้ปลายทาง เวลานี้แสดงเป็นจำนวนมิลลิวินาทีตั้งแต่ Unix epoch (1970-01-01T00:00:00Z)ไม่
updated_atnumberเวลาที่ข้อมูลของผู้ใช้ปลายทางถูกอัปเดตล่าสุด เวลานี้แสดงเป็นจำนวนมิลลิวินาทีตั้งแต่ Unix epoch (1970-01-01T00:00:00Z)ไม่

การอ้างสิทธิ์มาตรฐาน อื่น ๆ เช่น family_name, given_name, middle_name, nickname, preferred_username, profile, website, gender, birthdate, zoneinfo, และ locale จะถูกรวมอยู่ในขอบเขต profile ด้วยโดยไม่ต้องร้องขอ endpoint userinfo ความแตกต่างเมื่อเทียบกับการอ้างสิทธิ์ข้างต้นคือ การอ้างสิทธิ์เหล่านี้จะถูกส่งกลับมาเฉพาะเมื่อค่าของมันไม่ว่างเปล่า ในขณะที่การอ้างสิทธิ์ข้างต้นจะส่งกลับ null หากค่าเป็นค่าว่าง

บันทึก:

ต่างจากการอ้างสิทธิ์มาตรฐาน การอ้างสิทธิ์ created_at และ updated_at ใช้หน่วยเป็นมิลลิวินาทีแทนที่จะเป็นวินาที

email

ชื่อการอ้างสิทธิ์ประเภทคำอธิบายต้องใช้ userinfo หรือไม่?
emailstringอีเมลของผู้ใช้ไม่
email_verifiedbooleanอีเมลได้รับการยืนยันแล้วหรือไม่ไม่

phone

ชื่อการอ้างสิทธิ์ประเภทคำอธิบายต้องใช้ userinfo หรือไม่?
phone_numberstringหมายเลขโทรศัพท์ของผู้ใช้ไม่
phone_number_verifiedbooleanหมายเลขโทรศัพท์ได้รับการยืนยันแล้วหรือไม่ไม่

address

โปรดดูรายละเอียดของการอ้างสิทธิ์ที่อยู่ได้ที่ OpenID Connect Core 1.0

custom_data

ชื่อการอ้างสิทธิ์ประเภทคำอธิบายต้องใช้ userinfo หรือไม่?
custom_dataobjectข้อมูลกำหนดเองของผู้ใช้ใช่

identities

ชื่อการอ้างสิทธิ์ประเภทคำอธิบายต้องใช้ userinfo หรือไม่?
identitiesobjectข้อมูลตัวตนที่เชื่อมโยงของผู้ใช้ใช่
sso_identitiesarrayข้อมูล SSO ที่เชื่อมโยงของผู้ใช้ใช่

roles

ชื่อการอ้างสิทธิ์ประเภทคำอธิบายต้องใช้ userinfo หรือไม่?
rolesstring[]บทบาทของผู้ใช้ไม่

urn:logto:scope:organizations

ชื่อการอ้างสิทธิ์ประเภทคำอธิบายต้องใช้ userinfo หรือไม่?
organizationsstring[]รหัสองค์กรที่ผู้ใช้สังกัดไม่
organization_dataobject[]ข้อมูลขององค์กรที่ผู้ใช้สังกัดใช่

urn:logto:scope:organization_roles

ชื่อการอ้างสิทธิ์ประเภทคำอธิบายต้องใช้ userinfo หรือไม่?
organization_rolesstring[]บทบาทของผู้ใช้ในแต่ละองค์กรในรูปแบบ <organization_id>:<role_name>ไม่

เพื่อประสิทธิภาพและขนาดข้อมูล หาก "ต้องใช้ userinfo หรือไม่?" เป็น "ใช่" หมายความว่าการอ้างสิทธิ์นั้นจะไม่ปรากฏในโทเค็น ID แต่จะถูกส่งกลับใน response ของ userinfo endpoint

ทรัพยากร API

เราแนะนำให้อ่าน 🔐 การควบคุมการเข้าถึงตามบทบาท (RBAC) ก่อน เพื่อทำความเข้าใจแนวคิดพื้นฐานของ RBAC ใน Logto และวิธีตั้งค่าทรัพยากร API อย่างถูกต้อง

กำหนดค่า Logto client

เมื่อคุณตั้งค่า ทรัพยากร API (API resources) เรียบร้อยแล้ว คุณสามารถเพิ่มทรัพยากรเหล่านี้ขณะกำหนดค่า Logto ในแอปของคุณได้:

app/logto.ts
export const logtoConfig = {
// ...other configs
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'], // เพิ่มทรัพยากร API (Add API resources)
};

แต่ละ ทรัพยากร API (API resource) จะมี สิทธิ์ (scopes) ของตัวเอง

ตัวอย่างเช่น ทรัพยากร https://shopping.your-app.com/api มีสิทธิ์ shopping:read และ shopping:write และทรัพยากร https://store.your-app.com/api มีสิทธิ์ store:read และ store:write

หากต้องการร้องขอสิทธิ์เหล่านี้ คุณสามารถเพิ่มขณะกำหนดค่า Logto ในแอปของคุณได้:

app/logto.ts
export const logtoConfig = {
// ...other configs
scopes: ['shopping:read', 'shopping:write', 'store:read', 'store:write'],
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'],
};

คุณอาจสังเกตได้ว่า ขอบเขต (scopes) ถูกกำหนดแยกจาก ทรัพยากร API (API resources) นี่เป็นเพราะ Resource Indicators for OAuth 2.0 ระบุว่า ขอบเขตสุดท้ายสำหรับคำขอจะเป็นผลคูณคาร์ทีเซียนของขอบเขตทั้งหมดในบริการเป้าหมายทั้งหมด

ดังนั้น ในกรณีข้างต้น ขอบเขต (scopes) สามารถทำให้เรียบง่ายขึ้นจากการกำหนดใน Logto โดยทั้งสอง ทรัพยากร API (API resources) สามารถมีขอบเขต read และ write ได้โดยไม่ต้องมีคำนำหน้า จากนั้น ในการตั้งค่า Logto:

app/logto.ts
export const logtoConfig = {
// ...other configs
scopes: ['read', 'write'], // ขอบเขต (scopes) ที่ร้องขอ
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'], // ทรัพยากร (resources) ที่ร้องขอ
};

สำหรับแต่ละ ทรัพยากร API (API resource) จะร้องขอทั้งขอบเขต read และ write

บันทึก:

คุณสามารถร้องขอขอบเขต (scopes) ที่ไม่ได้กำหนดไว้ใน ทรัพยากร API (API resources) ได้ เช่น คุณสามารถร้องขอขอบเขต email ได้ แม้ว่า ทรัพยากร API (API resources) จะไม่มีขอบเขต email ให้ ขอบเขตที่ไม่มีจะถูกละเว้นอย่างปลอดภัย

หลังจากลงชื่อเข้าใช้สำเร็จ Logto จะออกขอบเขตที่เหมาะสมให้กับ ทรัพยากร API (API resources) ตามบทบาทของผู้ใช้

ดึงโทเค็นการเข้าถึงสำหรับทรัพยากร API

เพื่อดึงโทเค็นการเข้าถึง (Access token) สำหรับทรัพยากร API เฉพาะ คุณสามารถใช้เมธอด getAccessToken ได้ดังนี้:

บันทึก:

ไม่อนุญาตให้กำหนด Server Actions ที่มี annotation "use server" แบบอินไลน์ใน Client Components เราต้องส่งผ่านผ่าน props จาก Server Component เท่านั้น

app/page.ts
import { getAccessToken } from '@logto/next/server-actions';
import { logtoConfig } from './logto';
import GetAccessToken from './get-access-token';

export default async function Home() {
return (
<main>
<GetAccessToken
onGetAccessToken={async () => {
'use server';

return getAccessToken(logtoConfig, 'https://shopping.your-app.com/api');
}}
/>
</main>
);
}
app/get-access-token.ts
'use client';

// ประเภทของพร็อพส์
type Props = {
onGetAccessToken: () => Promise<string>;
};

const GetAccessToken = ({ onGetAccessToken }: Props) => {
return (
<button
onClick={async () => {
const token = await onGetAccessToken();
console.log(token);
}}
>
รับโทเค็นการเข้าถึง (ดูที่ console log)
</button>
);
};

export default GetAccessToken;

เมธอดนี้จะส่งคืนโทเค็นการเข้าถึง (JWT access token) ที่สามารถใช้เข้าถึงทรัพยากร API ได้เมื่อผู้ใช้มีสิทธิ์ที่เกี่ยวข้อง หากโทเค็นการเข้าถึงที่แคชไว้หมดอายุแล้ว เมธอดนี้จะพยายามใช้โทเค็นรีเฟรช (Refresh token) เพื่อขอโทเค็นการเข้าถึงใหม่โดยอัตโนมัติ

หากคุณต้องการดึงโทเค็นการเข้าถึงใน server component สามารถใช้ฟังก์ชัน getAccessTokenRSC ได้:

app/page.tsx
import { getAccessTokenRSC } from '@logto/next/server-actions';
import { logtoConfig } from './logto';

export default async function Home() {
const accessToken = await getAccessTokenRSC(logtoConfig, 'https://shopping.your-app.com/api');

return (
<main>
<p>Access token: {accessToken}</p>
</main>
);
}
เคล็ดลับ:

HTTP ไม่อนุญาตให้ตั้งค่า cookie หลังจากเริ่ม streaming แล้ว getAccessTokenRSC จะไม่สามารถอัปเดตค่า cookie ได้ ดังนั้นหากโทเค็นการเข้าถูกรีเฟรช จะไม่ถูกบันทึกใน session แนะนำให้ใช้ฟังก์ชัน getAccessToken ที่ฝั่ง client หรือใน route handlers

ดึงโทเค็นองค์กร

หากคุณยังไม่คุ้นเคยกับ องค์กร (Organization) โปรดอ่าน 🏢 องค์กร (หลายผู้เช่า; Multi-tenancy) เพื่อเริ่มต้น

คุณต้องเพิ่ม UserScope.Organizations ขอบเขต (scope) ขณะตั้งค่า Logto client:

app/logto.ts
import { UserScope } from '@logto/next';

export const logtoConfig = {
// ...other configs
// บรรทัดถัดไปคือจุดสำคัญ
scopes: [UserScope.Organizations],
};

เมื่อผู้ใช้ลงชื่อเข้าใช้แล้ว คุณสามารถดึงโทเค็นองค์กร (organization token) สำหรับผู้ใช้ได้:

บันทึก:

ไม่อนุญาตให้กำหนด Server Actions ที่มี annotation "use server" แบบอินไลน์ใน Client Components เราต้องส่งผ่านผ่าน props จาก Server Component เท่านั้น

app/page.ts
import { getOrganizationToken } from '@logto/next/server-actions';
import { logtoConfig } from './logto';
import GetOrganizationToken from './get-organization-token';

export default async function Home() {
return (
<main>
<GetOrganizationToken
onGetOrganizationToken={async () => {
'use server';

return getOrganizationToken(logtoConfig, 'organization-id');
}}
/>
</main>
);
}
app/get-organization-token.ts
'use client';

type Props = {
onGetOrganizationToken: () => Promise<string>;
};

const GetOrganizationToken = ({ onGetOrganizationToken }: Props) => {
return (
<button
onClick={async () => {
const token = await onGetOrganizationToken();
console.log(token);
}}
>
รับโทเค็นองค์กร (ดูที่ console log)
</button>
);
};

export default GetOrganizationToken;

หากคุณต้องการดึงโทเค็นองค์กรใน server component สามารถใช้ฟังก์ชัน getOrganizationTokenRSC ได้:

app/page.tsx
import { getOrganizationTokenRSC } from '@logto/next/server-actions';
import { logtoConfig } from './logto';

export default async function Home() {
const token = await getOrganizationTokenRSC(logtoConfig, 'organization-id');

return (
<main>
<p>Organization token: {token}</p>
</main>
);
}
เคล็ดลับ:

HTTP ไม่อนุญาตให้ตั้งค่า cookie หลังจากเริ่ม streaming แล้ว getOrganizationTokenRSC จะไม่สามารถอัปเดตค่า cookie ได้ ดังนั้นหากโทเค็นการเข้าถูกรีเฟรช จะไม่ถูกบันทึกใน session แนะนำให้ใช้ฟังก์ชัน getOrganizationToken ที่ฝั่ง client หรือใน route handlers

ใช้ external session storage

SDK จะใช้คุกกี้ในการจัดเก็บข้อมูลเซสชันที่ถูกเข้ารหัสโดยค่าเริ่มต้น วิธีนี้มีความปลอดภัย ไม่ต้องใช้โครงสร้างพื้นฐานเพิ่มเติม และเหมาะอย่างยิ่งกับสภาพแวดล้อมแบบ serverless เช่น Vercel

อย่างไรก็ตาม บางครั้งคุณอาจต้องจัดเก็บข้อมูลเซสชันภายนอก เช่น เมื่อข้อมูลเซสชันของคุณมีขนาดใหญ่เกินกว่าที่คุกกี้จะรองรับได้ โดยเฉพาะเมื่อคุณต้องรักษาเซสชันขององค์กรหลายรายการพร้อมกัน ในกรณีเหล่านี้ คุณสามารถใช้ external session storage ได้โดยใช้ตัวเลือก sessionWrapper:

import { MemorySessionWrapper } from './storage';

export const config = {
// ...
sessionWrapper: new MemorySessionWrapper(),
};
import { randomUUID } from 'node:crypto';

import { type SessionWrapper, type SessionData } from '@logto/next';

export class MemorySessionWrapper implements SessionWrapper {
private readonly storage = new Map<string, unknown>();

async wrap(data: unknown, _key: string): Promise<string> {
const sessionId = randomUUID();
this.storage.set(sessionId, data);
return sessionId;
}

async unwrap(value: string, _key: string): Promise<SessionData> {
if (!value) {
return {};
}

const data = this.storage.get(value);
return data ?? {};
}
}

ตัวอย่างข้างต้นใช้การจัดเก็บข้อมูลแบบ in-memory อย่างง่าย ในสภาพแวดล้อม production คุณอาจต้องการใช้โซลูชันการจัดเก็บข้อมูลที่คงทนมากขึ้น เช่น Redis หรือฐานข้อมูล

อ่านเพิ่มเติม

กระบวนการสำหรับผู้ใช้ปลายทาง: กระบวนการยืนยันตัวตน, กระบวนการบัญชี, และกระบวนการองค์กร ตั้งค่าตัวเชื่อมต่อ การอนุญาต (Authorization)