Chuyển tới nội dung chính

Tích hợp đăng nhập (SSO)

Cho phép người dùng đã đăng nhập ở hệ thống của bạn vào thẳng khung chat với đúng danh tính, không phải đăng nhập lại.


1. Cách hoạt động​

Backend của bạn đứng ra xác nhận danh tính người dùng với RM Platform; nền tảng cấp cho họ một phiên riêng:

  1. Backend của bạn ký một identity assertion — JWT ngắn hạn theo RFC 7523 — cho người dùng đang đăng nhập.
  2. Assertion được đổi lấy phiên tại POST {PLATFORM_URL}/api/oauth2/token với grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer. Nền tảng xác minh chữ ký bằng client_secret của Ứng dụng OAuth, tìm (hoặc mở) tài khoản khách tương ứng, trả access_token + refresh_token (+ id_token).
  3. Khung chat dùng phiên này để trò chuyện.

Với khung nhúng sẵn (bong bóng, iframe, live chat), bước 2 và 3 do khung chat tự làm — bạn chỉ đưa assertion cho nó. Với giao diện tự dựng, bạn gọi endpoint ở bước 2.

Đây là cơ chế chuẩn

Nền tảng là một OAuth 2.0 / OpenID Connect provider: GET {PLATFORM_URL}/.well-known/openid-configuration liệt kê mọi endpoint và grant. Assertion theo RFC 7523 nên thư viện JWT nào cũng ký được.


2. Hai URL cần phân biệt​

  • PLATFORM_URL — nền tảng Simplize, ví dụ https://app.simplize.dev. Với khung nhúng sẵn, mọi lời gọi phía này do khung chat tự lo.
  • URL ứng dụng của bạn — backend của bạn, ví dụ https://your-app.com. Đây là nơi bạn tự viết endpoint phát assertion.
Endpoint (ví dụ, của bạn)Công dụngBắt buộc
GET /agent-assertionKý và trả về identity assertion cho người dùng đang đăng nhậpCó
Quy tắc nhớ nhanh

Endpoint phát assertion nằm ở backend của bạn. Mọi đường dẫn còn lại nằm ở PLATFORM_URL.


3. Luồng tổng quan​

Người dùng (đã đăng nhập ở hệ thống của bạn)
│
▼
[1] Khung chat nhúng sẵn khởi động, hỏi danh tính
│
▼
[2] Site của bạn lấy assertion ──► GET {YOUR_APP_URL}/agent-assertion
│ (backend ký JWT RFC 7523 bằng client_secret)
▼ truyền { jwt, clientId } vào khung chat
┌─────────────────────────────────────────────────────────────┐
│ Khung chat tự làm phần còn lại: │
│ • POST {PLATFORM_URL}/api/oauth2/token (grant jwt-bearer) │
│ • hiển thị hội thoại, làm mới phiên khi cần │
└─────────────────────────────────────────────────────────────┘

client_secret chỉ tồn tại ở server; site chỉ giữ assertion đã ký nên không thể giả mạo danh tính. Access token / channel ID là định danh công khai, không tham gia xác thực.


4. Các bước tích hợp​

Tạo Ứng dụng OAuth ở Portal

Vào Cài đặt → Cấu hình chung → Ứng dụng OAuth → Tạo ứng dụng, xác nhận mật khẩu, đặt tên. Hệ thống cấp:

TrườngMô tảLưu trữ
client_idĐịnh danh ứng dụng OAuth (công khai, dạng pc_…)Có thể để ở FE
client_secretKhoá ký assertion — hiện một lần; xem lại / đổi phải xác nhận mật khẩuChỉ ở server
Môi trường dev

Trên môi trường Development, Simplize cấp và gửi cặp client_id / client_secret cho bạn.

Cấu hình ở hệ thống của bạn

Lưu client_secret ở server (biến môi trường hoặc secret manager) — không commit, không đưa xuống FE. Lưu kèm PLATFORM_URL, client_id, và access token / channel ID (công khai).

Phát identity assertion (RFC 7523)

Tạo một endpoint nội bộ (yêu cầu đăng nhập) ký một JWT HS256 bằng client_secret với đúng các claim sau:

ClaimGiá trịBắt buộc
issclient_id của bạnCó
subMã người dùng trong hệ thống của bạn (ổn định, không đổi)Có
aud{PLATFORM_URL}/api/oauth2/tokenCó
jtiChuỗi ngẫu nhiên duy nhất — nền tảng đốt sau lần dùng đầuCó
iat, expPhát lúc nào, hết hạn khi nào — khuyến nghị 5 phút, tối đa 1 giờCó
emailĐịa chỉ của người dùng — dùng làm tên đăng nhập lần đầuNên có
nameTên hiển thịNên có
{
"iss": "pc_your_client_id",
"sub": "45",
"aud": "https://app.simplize.dev/api/oauth2/token",
"jti": "5f2c9a1e-…",
"iat": 1700000000,
"exp": 1700000300,
"email": "user@example.com",
"name": "Nguyễn Văn A"
}
const jwt = require('jsonwebtoken');
const { randomUUID } = require('crypto');

const PLATFORM_URL = process.env.PLATFORM_URL; // https://app.simplize.dev

app.get('/agent-assertion', requireLogin, (req, res) => {
const token = jwt.sign(
{ sub: String(req.user.id), email: req.user.email, name: req.user.name },
process.env.CLIENT_SECRET,
{
algorithm: 'HS256',
issuer: process.env.CLIENT_ID,
audience: `${PLATFORM_URL}/api/oauth2/token`,
jwtid: randomUUID(),
expiresIn: '5m',
},
);
res.json({ jwt: token, clientId: process.env.CLIENT_ID });
});
Ký bằng khoá riêng thay vì bí mật chung

Nếu không muốn chia sẻ bí mật, khai jwks_uri trên Ứng dụng OAuth (phương thức private_key_jwt) và ký assertion RS256 bằng khoá riêng của bạn — nền tảng xác minh bằng khoá công khai bạn công bố. Các claim giữ nguyên.

Truyền assertion vào khung chat

Đưa { jwt, clientId } cho khung chat nhúng sẵn — khung chat tự đổi lấy phiên. Cách truyền cho bong bóng, iframe và live chat xem tại Nhúng vào trang web → Tự động đăng nhập.


5. Tích hợp trực tiếp qua API (giao diện tự dựng)​

Nếu bạn không dùng khung nhúng sẵn, đổi assertion lấy phiên rồi gọi API bằng access_token.

5.1 Đổi assertion lấy phiên​

POST {PLATFORM_URL}/api/oauth2/token — thân application/x-www-form-urlencoded (JSON cũng được nhận). Assertion là chứng thực của client (RFC 7523 §2.1), không cần gửi client_secret — nên gọi được từ trình duyệt.

curl -X POST https://app.simplize.dev/api/oauth2/token \
-d 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \
-d 'client_id=pc_your_client_id' \
-d 'assertion=<JWT do backend của bạn ký>' \
-d 'scope=openid email'

Trả về theo RFC 6749 §5.1:

{
"access_token": "eyJ…",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "5ca4…",
"scope": "openid email",
"id_token": "eyJ…"
}
  • access_token sống 15 phút; refresh_token xoay vòng mỗi lần làm mới.
  • id_token là JWT RS256 — xác minh bằng {PLATFORM_URL}/.well-known/jwks.json, aud = client_id của bạn, sub = mã người dùng trên nền tảng.
  • Cùng một sub trong assertion luôn về cùng một tài khoản; lần đầu thấy sub mới, nền tảng mở tài khoản khách với email/name trong assertion.
// Trình duyệt: lấy assertion từ backend của bạn rồi đổi lấy phiên
const { jwt, clientId } = await fetch('/agent-assertion', { credentials: 'include' }).then((r) => r.json());
const session = await fetch(`${PLATFORM_URL}/api/oauth2/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
client_id: clientId,
assertion: jwt,
scope: 'openid email',
}),
}).then((r) => r.json());
// session.access_token → header Authorization: Bearer …

5.2 Làm mới phiên​

Từ trình duyệt (không có client_secret):

curl -X POST https://app.simplize.dev/api/auth/refresh \
-H 'Content-Type: application/json' \
-d '{"refresh_token": "<refresh_token>"}'

Từ server (có client_secret) có thể dùng grant chuẩn: POST /api/oauth2/token với grant_type=refresh_token&client_id=…&client_secret=…&refresh_token=….

Cả hai trả access_token mới kèm refresh_token mới. Refresh token cũ bị thu hồi ngay khi xoay; dùng lại token đã xoay sẽ huỷ mọi phiên của người đó (dấu hiệu bị đánh cắp).

5.3 Người dùng là ai​

curl https://app.simplize.dev/api/oauth2/userinfo -H 'Authorization: Bearer <access_token>'

Trả sub, email, name theo scope đã xin.

5.4 Đăng xuất​

curl -X POST https://app.simplize.dev/api/auth/logout -H 'Authorization: Bearer <access_token>'

5.5 Gọi khung chat AI bằng phiên​

Các endpoint {PLATFORM_URL}/chat-app/{token}/… (parameters, conversations, messages, chat-messages) nhận Authorization: Bearer <access_token> — người dùng vào đúng danh tính của họ. Chi tiết endpoint chat sẽ được bổ sung.


6. Bảo mật​

  • client_secret chỉ ở server — không đưa xuống FE dưới bất kỳ dạng nào.
  • client_id, access token, channel ID là định danh công khai — lộ cũng không thể ký giả assertion.
  • Assertion: exp ngắn (5 phút), jti duy nhất — nền tảng từ chối dùng lại cùng một assertion; aud phải là đúng token endpoint của nền tảng.
  • Endpoint phát assertion phải yêu cầu đăng nhập — mỗi người chỉ phát assertion cho chính mình; sub phải là mã ổn định, đừng dùng email làm sub nếu người dùng có thể đổi email.
  • Nền tảng không tự nối một tài khoản có sẵn vào assertion chỉ vì trùng email: nếu email đã thuộc một tài khoản đăng ký theo cách khác, lần đầu sẽ bị từ chối — hãy dùng địa chỉ mà chỉ hệ thống của bạn xác nhận.

7. Mã lỗi​

Lỗi trả theo RFC 6749 §5.2: {"error": "...", "error_description": "..."}.

HTTPerrorÝ nghĩaCách xử lý
401invalid_clientclient_id không tồn tại, bị tắt, hoặc IP không được phépKiểm tra client_id; danh sách IP trên Ứng dụng OAuth
400invalid_grantAssertion sai chữ ký, hết hạn, sai aud/iss, jti đã dùng, hoặc sống quá 1 giờKý assertion mới, đúng claim; không dùng lại jti
400unsupported_grant_typegrant_type không đúng chuỗiDùng urn:ietf:params:oauth:grant-type:jwt-bearer
400invalid_scopeScope xin không nằm trong scope của Ứng dụng OAuthXin đúng scope đã khai
404—Ứng dụng chat chưa bật chia sẻ hoặc chưa publishBật chia sẻ và publish ứng dụng