指南

快速開始

快速開始:完整 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 流程。