Applogin Dashboard

Applogin — 统一登录

只需一段脚本,即可为您的应用添加 Google 和邮箱/密码登录。全部认证 — 登录、注册、邮箱验证、密码重置、Google — 都在 applogin.one 上受保护的 iframe 内完成。您的页面永远看不到用户密码,也不做任何校验 — 您只会收到一个开箱即用的应用专属令牌

平台为您做的事
  • 渲染登录/注册表单(在其自己的 iframe 中)— 您无需自己构建
  • 接收密码、校验凭证、发送并验证邮箱验证码 — 您什么都不用验证
  • 每个邮箱只维护一个账号(Google 和密码指向同一账号)。
  • 维护跨应用会话,在您的多个应用之间自动接续登录。

工作原理

Applogin 是一个认证提供方(类似 Auth0/Clerk),但登录表单是从我们的域名以 frame 形式提供的。您的应用通过 SDK 以模态框打开该 frame,并通过 postMessage 接收结果。令牌是用您的应用密钥签名的 JWT(aud=app_id),在任何其他应用中都无效。

您的应用(例如 emailwriter.online) │ applogin.js + applogin-ui.js (SDK / 组件) │ 打开带 iframe 的模态框: ▼ ┌──────────────────────────────────────────┐ │ iframe → https://applogin.one/login │ ← 密码在这里输入 │ (表单: Google / 邮箱+密码 / 重置) │ 您的 JS 看不到 └──────────────────────────────────────────┘ ▲ postMessage { token, user } (仅发往您的来源) │ ▼ 您收到应用令牌 → 在后端验证 (/v1/session/verify)
概念说明
应用 (app)平台上的实体。拥有公开的 app_idjwt_secret(后端令牌验证)以及 allowed_origins / redirect_uris 列表。
终端用户平台所有应用共享的全局账号(每个邮箱一个)。Google 和密码关联到同一账号。
应用令牌用您应用的 jwt_secret 签名的 JWT(HS256,aud=app_id)。背后是可吊销的会话。
SSO 会话存于 applogin.one 上 HttpOnly+Partitioned Cookie al_sso 中的全局会话。在同一顶级站点内共享。

快速开始

引入两个脚本并挂载组件 — 它会静默尝试 SSO,显示"登录"按钮,在 frame 中打开托管表单并返回用户。

<script src="https://applogin.one/applogin.js?v=1"></script>
<script src="https://applogin.one/applogin-ui.js?v=1"></script>
<script>
  ApploginUI.mount({
    appId: "app_your_id",
    onAuth:   (user) => renderApp(user),  // signed in: { id, email, name, picture, email_verified }
    onLogout: ()     => showLanding()
  });
  // sign-out button:  ApploginUI.logout();
</script>

就这些。表单、注册、邮箱验证码、Google 和密码重置全部由 Applogin 处理。唯一的要求是注册一个 app_id 并配置您的 allowed_origins(见创建应用)。

脚本 URL 中的 ?v=1 版本号可防止浏览器在更新后返回缓存的旧版 SDK。

ApploginUI 组件 推荐

为您的应用准备的现成"门锁":未登录时显示带"登录"按钮的屏幕;点击后在模态框中打开托管表单;登录后调用 onAuth(user) 并移除屏幕。

方法说明
ApploginUI.mount({ appId, onAuth, onLogout, title? })挂载。静默尝试 SSO;未登录则渲染"登录"屏幕。
ApploginUI.logout()退出登录(吊销会话,重新显示屏幕)。
ApploginUI.getUser()当前用户或 null

个人资料(头像、姓名、邮箱)和退出按钮由您在 onAuth(user) 中自行渲染 — 那是您的 UI。用户数据到手即可使用。

SDK 方法(applogin.js低层级

如果您想要自己的 UX 而不是组件,可直接使用 SDK。先调用 Applogin.init({ appId })。令牌保存在 localStorageal_token_<appId> 下。

方法说明返回
init({ appId })初始化。
getSession()静默 SSO:若用户已在同一站点的其他应用登录,则接续会话。{ authenticated, token, user }
openLogin()打开托管登录表单模态框(applogin.one 上的 iframe)。{ token, user, lang }null
verify()验证当前令牌。{ valid, user }
logout()退出(吊销会话并清除令牌)。
getToken()当前应用令牌。string | null
Applogin.init({ appId: "app_xxx" });

// on load — silently try SSO:
const s = await Applogin.getSession();
if (s.authenticated) showApp(s.user);
else {
  const r = await Applogin.openLogin();     // modal with the applogin.one form
  if (r) showApp(r.user);
}
// attach Applogin.getToken() to your backend requests

单点登录(SSO)

登录时,Applogin 会设置带 Partitioned 属性的 Cookie al_sso。分区键是顶级站点(最外层)。因此:

const s = await Applogin.getSession();   // ← {authenticated:true,…} if a session exists in this partition
if (s.authenticated) showApp(s.user);
allowed_origins 要求。登录 frame 会检查整条祖先链(frame-ancestors),因此应用的 allowed_origins 必须同时列出其自身域名和外层包装域名(例如 appdock.pro)。
关于隔离的诚实说明。"同一站点 → SSO,其他顶级站点 → 重新登录"的区分由浏览器(Cookie 分区,CHIPS)强制执行。在不支持 CHIPS 的浏览器中,Cookie 可能表现为共享 — 此时应用的独立页面也会自动登录。这不是漏洞:令牌的发放仍由 allowed_origins 把关(无关站点永远拿不到令牌)。

语言

托管登录表单支持 10 种语言:English, Deutsch, Français, Español, Italiano, Português, 日本語, 한국어, 中文, Русский。默认按用户的浏览器语言打开(回退为英语);用户可直接在表单中切换语言 — 底部的国旗菜单。

底层上,表单 URL 接受 ?lang=xx(上表中的两位代码)— SDK 会替您传递。

在线示例:AppDock

appdock.pro 是一个"坞站"式启动器:一个页面通过 iframe 打开多个应用。应用各自运行在自己的域名上:

应用域名app_id
Email Writeremailwriter.onlineapp_emailwriter
Audio Recorderaudiorecorder.infoapp_recorder

在坞站内登录 Email Writer,然后打开(同样在坞站内的)Audio Recorder — 登录会自动接续:二者都运行在同一顶级站点 appdock.pro 之下,共享一个 SSO 分区。

1. 坞站启动器(appdock.pro)

<button class="tile" data-src="https://emailwriter.online/">Email Writer</button>
<button class="tile" data-src="https://audiorecorder.info/">Audio Recorder</button>
<iframe id="frame" allow="microphone; clipboard-write"></iframe>
<script>
  document.querySelectorAll(".tile").forEach(t =>
    t.onclick = () => document.getElementById("frame").src = t.dataset.src);
</script>

2. 应用(例如 emailwriter.online)

<script src="https://applogin.one/applogin.js?v=1"></script>
<script src="https://applogin.one/applogin-ui.js?v=1"></script>
<script>
  ApploginUI.mount({
    appId: "app_emailwriter",
    title: "Email Writer — sign in",
    onAuth: (user) => {              // render the profile + the app
      header.textContent = user.name + " · " + user.email;
      app.hidden = false;
    },
    onLogout: () => { app.hidden = true; }
  });
</script>

3. 此场景下的应用注册

每个应用的 allowed_origins 都列出自身域名 + 坞站域名(frame-ancestors 会看到嵌套 frame 的整条祖先链):

app_emailwriter → allowed_origins: ["https://emailwriter.online", "https://appdock.pro"]
app_recorder    → allowed_origins: ["https://audiorecorder.info", "https://appdock.pro"]
// redirect_uris (for Google): ["https://applogin.one/auth-callback.html"]
最终行为:在坞站内 — Email Writer 与 Audio Recorder 共享一次登录;单独打开每个应用(其域名作为顶级站点)— 需重新登录。应用本身不含任何登录表单 — 只有 ApploginUI.mount

在您的后端验证令牌

前端将 Applogin.getToken() 放入请求的 Authorization 头。后端有两种验证方式。

1. 通过平台端点 考虑吊销

POST https://applogin.one/api/v1/session/verify
{ "app_id": "app_xxx", "token": "<JWT from Authorization>" }
// → { "valid": true, "user": { "id": 2, "email": "…", "email_verified": true, "name": "…", "picture": null } }
//   invalid/revoked (logout) → { "valid": false, "user": null }

2. 本地验证(用您的 jwt_secret 做 HS256)无需网络

// Node.js
const jwt = require("jsonwebtoken");
const claims = jwt.verify(token, process.env.APPLOGIN_JWT_SECRET, {
  algorithms: ["HS256"], audience: "app_xxx", issuer: "applogin"
});
// claims.sub — user id, claims.email, claims.email_verified

# Python
import jwt
claims = jwt.decode(token, APPLOGIN_JWT_SECRET, algorithms=["HS256"],
                    audience="app_xxx", issuer="applogin")
本地验证看不到会话的即时吊销(退出登录)。如需即时吊销,请使用 /v1/session/verify

JWT 结构

{
  "iss": "applogin",
  "aud": "app_xxx",          // your app_id
  "sub": "2",                // user id (as a string)
  "jti": "…",                // session id (for revocation)
  "email": "user@example.com",
  "email_verified": true,
  "name": "Jane", "picture": null,
  "iat": 1782194619, "exp": 1784786619   // ~30 days
}

Webhook(可选)

如果应用设置了 webhook_url,平台会在每次 Google 登录成功时向其发送带有已验证身份的签名 POST — 在令牌之外用于服务端同步。

请求头
X-Applogin-Timestampunix-ms
X-Applogin-Signaturetimestamp + "." + rawBodysha256=<hex>
const exp = "sha256=" + crypto.createHmac("sha256", WEBHOOK_SIGNING_SECRET)
  .update(ts + "." + rawBody).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(exp), Buffer.from(sig))) reject();

创建应用

应用在私有控制台 applogin.one/dashboard 中创建(用 Applogin 自身登录)。您需要提供:

字段用途
name显示在登录表单中("登录 · <name>")。
allowed_origins允许嵌入表单并接收令牌的来源。列出应用域名 + 外层包装域名(例如坞站)。
redirect_uris用于 Google 登录:https://applogin.one/auth-callback.html
webhook_url(可选)登录 Webhook 的接收地址。

您将获得:app_id(公开)、jwt_secret(后端令牌验证)、client_secretwebhook_signing_secret

REST API — 参考

基址:https://applogin.one/api。以下是开发者真正需要的。邮箱/密码和 Google 交换端点由托管表单自己调用 — 您永远不用调用。

供开发者使用

POST/v1/session/verify

在后端验证应用令牌(考虑吊销)。{ app_id, token }{ valid, user }

GET/v1/app/public

应用的公开数据(nameallowed_origins)。由托管登录页使用。

内部使用(由托管表单/SDK 调用,不是您)

端点角色
POST /v1/email/register · verify · resend · login · reset/request · reset/confirm · password/change邮箱/密码 — 由托管表单 /login 调用。
POST /v1/auth/start, GET /oauth/google/callback, POST /v1/auth/exchangeGoogle OAuth(在托管表单内部)。
POST /v1/session/sso静默 SSO(由隐藏 iframe 中的 /session 调用)。
POST /v1/session/establish在正确的分区中设置 SSO Cookie(Google 之后)。
POST /v1/session/logout吊销会话(通过 SDK logout())。

错误

HTTP场景
400输入无效(密码过短、验证码无效/过期、"Email already registered")。
403"Invalid credentials"、拒绝访问、"Too many attempts"(限流)。
404未知的应用/路由。
{"error":"Invalid or expired code","message":"Invalid or expired code"}

安全性

LLM 速查表

模型:所有认证都发生在 applogin.one 的 iframe 中。开发者不渲染表单、不发送密码、不校验验证码。开发者引入 SDK + 组件并接收应用令牌。

// include
<script src=https://applogin.one/applogin.js?v=1></script>
<script src=https://applogin.one/applogin-ui.js?v=1></script>

// mount (a lock on the app)
ApploginUI.mount({ appId, onAuth:(user)=>..., onLogout:()=>... });
ApploginUI.logout();

// low level
Applogin.init({ appId });
await Applogin.getSession();   // {authenticated, token, user} — silent SSO
await Applogin.openLogin();    // {token, user, lang}|null — modal with the form
Applogin.getToken(); await Applogin.verify(); await Applogin.logout();

// backend: verify the token
POST https://applogin.one/api/v1/session/verify {app_id, token} -> {valid, user}
// or locally: jwt.verify(token, jwt_secret, {algorithms:["HS256"], audience:app_id, issuer:"applogin"})

SSO:登录会设置 Cookie al_sso(Partitioned,按顶级站点分区)。同一站点下的应用(例如 appdock.pro 这类坞站的 iframe)共享会话;独立域名 → 重新登录。应用的 allowed_origins = 自身域名 + 外层包装域名。

令牌为应用专属:iss=apploginaud=app_idsub=用户 ID,用应用的 jwt_secret 签名。

Applogin · 统一登录 · 控制台 · 在线示例:AppDock