指南
快速開始
快速開始:完整 HTML 單頁
本頁提供 {{app_name}} 的完整 OAuth + PKCE 單頁範例。右側「範例」可直接複製存成 index.html,佔位符已替換為本 App 的公開資訊。
使用前
- 在 Auth 服務設定將回調 URI 加入白名單(建議:
{{web_origin}}/callback.html或您自己的路徑) - 切勿在瀏覽器 HTML 中放入
client_secret - 回調頁需與授權請求中的
redirect_uri完全一致 - Google 登入為 HSystem 平臺統一 Google(不需要、也不應該自行建立 Google Cloud Console Client)
流程
1. 使用者點「登入」→ 產生 PKCE verifier 並導向託管授權頁 2. 登入完成後帶 code 回到您的 HTML 3. 以 code + code_verifier 向 {{api_base}}/oauth/token 換取 access token
右側試用面板可在控制台內模擬一輪登入。
完整單頁 HTML(quickstart.html)
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{app_name}} · HSystem OAuth</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 40rem; margin: 2rem auto; padding: 0 1rem; }
button { padding: 0.5rem 1rem; font-size: 1rem; cursor: pointer; }
pre { background: #f4f4f5; padding: 1rem; overflow-x: auto; border-radius: 0.5rem; font-size: 0.85rem; }
.muted { color: #666; font-size: 0.9rem; }
</style>
</head>
<body>
<h1>{{app_name}}</h1>
<p class="muted">Client ID: {{client_id}} · API: {{api_base}}</p>
<button type="button" id="btn-login">以 HSystem 登入</button>
<pre id="out">尚未登入</pre>
<script>
const API_BASE = "{{api_base}}";
const CLIENT_ID = "{{client_id}}";
const REDIRECT_URI = "{{web_origin}}/callback.html";
const STORAGE_VERIFIER = "hs_pkce_verifier";
function b64url(bytes) {
let bin = "";
for (const b of bytes) bin += String.fromCharCode(b);
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function randomVerifier() {
return b64url(crypto.getRandomValues(new Uint8Array(32)));
}
async function codeChallenge(verifier) {
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
return b64url(new Uint8Array(hash));
}
async function startLogin() {
const verifier = randomVerifier();
sessionStorage.setItem(STORAGE_VERIFIER, verifier);
const challenge = await codeChallenge(verifier);
const u = new URL(API_BASE + "/oauth/authorize");
u.searchParams.set("client_id", CLIENT_ID);
u.searchParams.set("redirect_uri", REDIRECT_URI);
u.searchParams.set("response_type", "code");
u.searchParams.set("code_challenge", challenge);
u.searchParams.set("code_challenge_method", "S256");
u.searchParams.set("state", crypto.randomUUID());
location.href = u.toString();
}
async function handleCallback() {
const params = new URLSearchParams(location.search);
const code = params.get("code");
const err = params.get("error");
if (err) {
document.getElementById("out").textContent = "授權失敗: " + err;
return;
}
if (!code) return;
const verifier = sessionStorage.getItem(STORAGE_VERIFIER) || "";
const res = await fetch(API_BASE + "/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: REDIRECT_URI,
code_verifier: verifier,
client_id: CLIENT_ID,
}),
});
const data = await res.json();
document.getElementById("out").textContent = JSON.stringify(
res.ok ? { ok: true, token_type: data.token_type, expires_in: data.expires_in, access_token: data.access_token?.slice(0, 12) + "…" } : data,
null,
2,
);
history.replaceState({}, "", location.pathname);
}
document.getElementById("btn-login").addEventListener("click", startLogin);
handleCallback();
</script>
</body>
</html>目前為模擬模式,回應為範例資料
登入 Dev 以真實測試體驗託管登入頁
模擬預覽:下方為終端用戶在 OAuth 流程中看到的 HSystem 託管登入畫面。
https://auth.hsystem.halphastech.com/hosted/login
無法載入登入預覽,請確認 App 已啟用 Auth。
模擬預覽
登入 Dev 並從 App 頁面帶入 ?app= 即可切換為可互動的真實 OAuth 流程。

