指南
登入片段
登入片段(login-snippet)
將以下片段嵌入您的頁面,在使用者點擊「登入」時導向 HSystem 託管授權頁。需搭配 Callback 片段處理回調。
要點
- 使用 OAuth 授權碼 + PKCE(S256)
redirect_uri必須與 Auth 白名單完全一致code_verifier存於sessionStorage,回調頁讀取後換 token- iframe 嵌入可加
embed=1:成功不跳轉redirect_uri,改以postMessage回傳code - Google 登入為 HSystem 平臺統一 Google(開發者不需建立 App Google Client);且屬於 bind-first:終端用戶需先在
/account綁定 Google,否則會得到google_oauth_not_bound
login-snippet.html
<!-- 登入按鈕 -->
<button type="button" id="hs-login">以 HSystem 登入</button>
<script>
(function () {
const API_BASE = "{{api_base}}";
const CLIENT_ID = "{{client_id}}";
const REDIRECT_URI = "{{web_origin}}/callback.html";
const VERIFIER_KEY = "hs_pkce_verifier";
function b64url(bytes) {
let s = "";
for (const b of bytes) s += String.fromCharCode(b);
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
async function startOAuthLogin() {
const verifier = b64url(crypto.getRandomValues(new Uint8Array(32)));
sessionStorage.setItem(VERIFIER_KEY, verifier);
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
const challenge = b64url(new Uint8Array(hash));
const url = new URL(API_BASE + "/oauth/authorize");
url.searchParams.set("client_id", CLIENT_ID);
url.searchParams.set("redirect_uri", REDIRECT_URI);
url.searchParams.set("response_type", "code");
url.searchParams.set("code_challenge", challenge);
url.searchParams.set("code_challenge_method", "S256");
url.searchParams.set("state", crypto.randomUUID());
location.href = url.toString();
}
document.getElementById("hs-login").addEventListener("click", startOAuthLogin);
})();
</script>
iframe 模式(embed=1)
若你希望把託管登入頁嵌入 iframe(例如彈窗內),請在授權 URL 加上 embed=1。
- 成功:iframe 會對 parent 發送
postMessage({ type: "hsystem.oauth", code, state }, targetOrigin) - 失敗:
postMessage({ type: "hsystem.oauth.error", error, state }, targetOrigin) - 安全:
targetOrigin只會是 App 已登記的redirect_uri/allowed_origins白名單;不會使用*
另外,若在 iframe 內點擊 Google 登入,託管頁會優先用 popup/新分頁開啟 Google(保留 window.opener)。授權完成後,API 端回傳的 embed HTML 會先對 window.opener、再對 window.parent 發送相同的 postMessage,且在存在 opener 時會嘗試自動關閉視窗。
iframe 風格 query(URL 參數)
嵌入託管頁時可在 authorize → hosted 鏈路上的 query 保留下列參數(跨 /hosted/login、register、consent 會自動帶上):
| 參數 | 說明 | 範例 | |------|------|------| | theme | light 或 dark(預設跟系統;iframe 內可手動切換) | theme=dark | | primary | 主色 hex | primary=%2314b8a6 | | bg / surface / text / muted | 背景/卡片/文字 hex | surface=%23ffffff | | radius | sm / md / lg | radius=lg | | logo | 公司 logo(僅 https://) | logo=https%3A%2F%2Fcdn.example.com%2Flogo.png | | logo_alt | logo 替代文字 | logo_alt=MyApp |
未帶 logo 時顯示 HSystem 預設標誌。Google 登入按鈕為官方樣式,不受 primary 影響。
開發者試用頁(Docs Lab)可即時調整上述 query 並預覽 iframe 外觀。
示例(authorize URL 片段):
&embed=1&theme=dark&primary=%232dd4bf&logo=https%3A%2F%2Fcdn.example.com%2Flogo.png
iframe-login-snippet.html
<button type="button" id="hs-login">以 HSystem 登入(iframe)</button>
<div id="hs-modal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5);">
<div style="width:min(520px, 92vw); height:min(720px, 92vh); margin:4vh auto; background:#0b1220; border-radius:16px; overflow:hidden;">
<iframe
id="hs-frame"
title="HSystem Hosted Login"
style="width:100%; height:100%; border:0;"
referrerpolicy="no-referrer"
></iframe>
</div>
</div>
<script>
(function () {
const API_BASE = "{{api_base}}";
const CLIENT_ID = "{{client_id}}";
const REDIRECT_URI = "{{web_origin}}/callback.html";
const VERIFIER_KEY = "hs_pkce_verifier";
const apiOrigin = new URL(API_BASE).origin;
const modal = document.getElementById("hs-modal");
const frame = document.getElementById("hs-frame");
function b64url(bytes) {
let s = "";
for (const b of bytes) s += String.fromCharCode(b);
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
async function startIframeOAuth() {
const verifier = b64url(crypto.getRandomValues(new Uint8Array(32)));
sessionStorage.setItem(VERIFIER_KEY, verifier);
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
const challenge = b64url(new Uint8Array(hash));
const url = new URL(API_BASE + "/oauth/authorize");
url.searchParams.set("client_id", CLIENT_ID);
url.searchParams.set("redirect_uri", REDIRECT_URI);
url.searchParams.set("response_type", "code");
url.searchParams.set("code_challenge", challenge);
url.searchParams.set("code_challenge_method", "S256");
url.searchParams.set("state", crypto.randomUUID());
url.searchParams.set("embed", "1");
modal.style.display = "block";
frame.src = url.toString();
}
window.addEventListener("message", function (e) {
if (e.origin !== apiOrigin) return; // 只信任 API 來源
const data = e.data || {};
if (data.type === "hsystem.oauth") {
// 取得 code 後,請看 Callback 章節以 code+verifier 換 token
modal.style.display = "none";
console.log("OAuth code:", data.code, "state:", data.state);
}
if (data.type === "hsystem.oauth.error") {
modal.style.display = "none";
console.error("OAuth error:", data.error, "state:", data.state);
}
});
document.getElementById("hs-login").addEventListener("click", startIframeOAuth);
})();
</script>目前為模擬模式,回應為範例資料
登入 Dev 以真實測試體驗託管登入頁
模擬預覽:下方為終端用戶在 OAuth 流程中看到的 HSystem 託管登入畫面。
登入 Dev 並從 App 頁面帶入 ?app= 即可切換為可互動的真實 OAuth 流程。

