Xác thực
InfraIO Pay có hai surface API với mô hình auth khác nhau. Chọn cái khớp với người gọi:
| Surface | Path prefix | Đối tượng | Auth |
|---|---|---|---|
| B2B Merchant | /b2b/v1/* | Server của bạn | Ký request HMAC-SHA256 |
| Dashboard | Theo từng service: /auth/*, /payment/*, /merchant/*, /event/*, /user/*, … | Session trình duyệt cho dashboard merchant | Bearer JWT |
Trang này cover surface B2B — cái bạn gọi từ server với một cặp API key. Nếu bạn nhúng dashboard InfraIO hoặc xây tool nội bộ, dùng surface dashboard (tài liệu riêng, chưa public).
Gateway định tuyến mỗi surface theo prefix đầu tiên mà nó strip
trước khi forward: /b2b/v1/checkout-sessions/quick đến
payment-service dưới dạng /v1/checkout-sessions/quick, và
/payment/v1/orders của dashboard đến nó dưới dạng /v1/orders. Nên
nếu bạn thấy các path /v1/* trần ở nơi khác, đó là path backend
internal sau khi prefix public đã được loại bỏ — client của bạn luôn
gửi dạng có prefix. (Một hệ quả cho việc ký: chuỗi canonical B2B ký
path kèm prefix /b2b vẫn được gắn — xem dưới.)
Endpoints
| Môi trường | Base URL |
|---|---|
| Test | https://api-dev.infraio.xyz |
| Live | https://api.infraio.xyz |
Cùng một pattern URL — môi trường được điều khiển bởi prefix của
key (pk_test_… vs pk_live_…), không phải URL.
Cặp key
Bạn nhận được hai giá trị từ merchant dashboard (Developers → API keys → + Add key):
- Publishable key (
pk_test_…hoặcpk_live_…) — nhận diện tài khoản của bạn. Gửi dưới dạngX-Client-ID. An toàn để nhúng trong bundle trình duyệt của bạn (SDK đã làm điều này). - Secret key (
sk_test_…hoặcsk_live_…) — khóa ký HMAC. Chỉ phía server. Đối xử với nó như password database.
Nếu một secret key bao giờ lọt vào bundle trình duyệt, git repo, dòng log, hay chat chia sẻ — revoke ngay lập tức từ dashboard. Không có cửa sổ overlap; revoke có hiệu lực tức thời. Phát hành key mới và redeploy.
Ký một request
Mọi lệnh gọi đến /b2b/v1/* mang ba header:
X-Client-ID: pk_live_70d0becbd7a53047b0dfe88a960d10ad
X-Timestamp: 1715990400
X-Signature: 9a8b7c6d… (hex HMAC-SHA256)Chữ ký được tính trên một chuỗi canonical:
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODYMETHOD— HTTP verb viết hoa (POST,GET, …).PATH— path của request kèm prefix/b2b, không có host và không có query string (ví dụ/b2b/v1/checkout-sessions/quick). Gateway xác thực chữ ký trên raw path đến trước khi strip/b2b, nên prefix phải có mặt. Query parameter không được ký — vớiGET …?cursor=…&limit=20, chỉ ký path, không phải phần?….TIMESTAMP— unix seconds, dưới dạng chuỗi decimal (ví dụ"1715990400"), khớp chính xác vớiX-Timestamp.BODY— raw bytes của body request. Chuỗi rỗng choGET/DELETE.
Ký bằng HMAC-SHA256 với khóa secret, xuất hex:
Node / TS
import { createHmac } from "node:crypto";
function sign({ method, path, body, secret }: {
method: string; path: string; body: string; secret: string;
}) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const input = [method.toUpperCase(), path, timestamp, body].join("\n");
const signature = createHmac("sha256", secret).update(input).digest("hex");
return { timestamp, signature };
}Vì sao HMAC, không Bearer?
Một API Bearer-token thuần ship secret duy nhất qua dây trên mọi request. Bất kỳ ai bắt được một log của proxy TLS-terminated đều có chìa khóa tài khoản của bạn. Ký HMAC nghĩa là secret không bao giờ di chuyển — chỉ chữ ký được dẫn xuất, là single-use (gắn vào đúng request đó + đúng phút đó).
Đánh đổi: bạn phải tính chữ ký cho mỗi lệnh gọi. Một SDK server sẽ ẩn điều này; cho đến khi chúng tôi public một SDK, helper ở trên là ~15 dòng theo từng ngôn ngữ.
Tolerance timestamp
Tolerance ràng buộc là ±5 phút (300 giây), được enforce bởi
merchant-service khi nó xác thực chữ ký. Bản thân gateway lỏng hơn
một chút (310s) như là defence-in-depth, nhưng một request đi qua
gateway và fail kiểm tra bên trong vẫn kết thúc với
401 invalid_signature — coi 300s là contract. Hai hệ quả:
- Đồng bộ đồng hồ server với NTP. Một cron chạy dài hạn với đồng hồ trôi sẽ fail không đều.
- Đừng pre-compute và xếp hàng đợi chữ ký. Nếu một request nằm trong queue retry >5 phút, chữ ký của nó hết hạn.
Scope của key
Secret key mang một hoặc nhiều bundle scope sau:
| Scope | Use case dự kiến |
|---|---|
read | List/đọc orders, sessions, refunds |
write_order | Tạo checkout sessions, orders |
write_refund | Phát hành refund, mint token refund-request |
webhook_manage | Tạo/cập nhật/xóa webhook endpoint |
Dashboard phát hành key “full access” mặc định (cả bốn scope). Bạn có thể phát hành key scope hạn chế từ Developers → API keys → + Add key và chỉ tick các scope mà tích hợp cần.
Enforce scope hiện tại là advisory, không gate. Scope được ghi
trên key và hiển thị lại cho bạn trong dashboard, nhưng middleware
của gateway hiện chưa reject các lệnh gọi out-of-scope — bất kỳ key
sk_… hợp lệ nào đều hoạt động như full-access hôm nay. Scope gate
theo từng endpoint nằm trong bản phát hành tiếp theo. Đừng dựa vào
scope như là ranh giới bảo mật chưa; coi chúng như nhãn và rotate /
revoke key để hạn chế truy cập trong thời gian chờ.
Chữ ký được xác thực ở đâu
Xác thực HMAC xảy ra một lần, tại gateway. Gateway:
- Đọc
X-Client-ID,X-Timestamp,X-Signature. - Tra cứu merchant + secret qua
pk_…, chạy kiểm tra cửa sổ timestamp, tính lại chữ ký, so sánh constant-time. - Khi thành công, strip auth header, đóng dấu request với header
nội bộ (
X-B2B-Auth: 1,X-Merchant-ID,X-Merchant-Domain) và forward đến downstream service (payment-service, merchant-service, v.v.). Môi trường + scope đã resolve KHÔNG được inject hôm nay — code downstream cần môi trường dẫn xuất từ body request / cấu hình theo merchant, không phải từ header. - Khi thất bại, trả về 401
INVALID_SIGNATUREmà không bao giờ chạm vào backend.
Downstream service không chạy lại HMAC — chúng tin tưởng header
gateway đã inject và hành động trên merchant mà gateway đã resolve.
Chúng cũng không scope-gate theo từng endpoint: như đã lưu ý ở
trên, scope của key không được inject, nên bất kỳ sk_… đã xác thực
nào cũng đến được bất kỳ endpoint nào cho merchant của nó (enforce
scope là advisory hôm nay — xem callout dưới Scope của key).
Điều này quan trọng theo hai cách:
- Nếu bạn vận hành reverse proxy của riêng mình trước InfraIO Pay,
đừng strip
X-B2B-Auth/X-Merchant-ID(và cũng đừng forge chúng — gateway reject các inbound request mang chúng ở public edge). - Path public-network (
/b2b/v1/*) là surface duy nhất chạy bước HMAC. gRPC nội bộ giữa các service của chúng tôi dùng mTLS — một mô hình tin cậy khác không chấp nhậnX-Client-ID.
Tiếp theo
- Lỗi — hình dáng response trên 4xx/5xx.
- Bảo mật → API keys — rotate, revoke, làm gì nếu secret bị lộ.
- Webhooks → Xác thực chữ ký
— dùng một scheme HMAC khác (header
X-Signature: sha256=…, kýX-Timestamp + "." + raw_body, cộng thêmX-Signature-Prevtùy chọn trong cửa sổ grace 24 giờ khi rotate). Đừng nhầm hai scheme — chúng dùng chung thuật toán hash nhưng bytes được ký và họ secret (whsec_…vssk_…) là khác nhau.