Applogin — unified sign-in
Add Google and email/password sign-in to your application with one script. All authentication — sign-in, sign-up, email verification, password reset, Google — happens inside a protected iframe on applogin.one. Your page never sees the user's password and never validates anything — you only receive a ready-made app-scoped token.
- Renders the sign-in/sign-up form (in its own iframe) — you don't build one.
- Accepts the password, checks credentials, sends and verifies email codes — you verify nothing.
- Keeps one user account per email (Google and password map to the same account).
- Maintains a cross-app session and silently picks the sign-in up between your apps.
How it works
Applogin is an authentication provider (like Auth0/Clerk), but the login form is served in a frame from our domain. Your app opens that frame (in a modal) through the SDK and receives the result via postMessage. The token is a JWT signed with your application's secret (aud=app_id), so it is useless in any other application.
| Concept | What it is |
|---|---|
| Application (app) | An entity on the platform. Has a public app_id, a jwt_secret (backend token verification) and the allowed_origins / redirect_uris lists. |
| End user | A global account (one per email) shared by all platform applications. Google and password link to the same account. |
| App token | JWT (HS256, aud=app_id) signed with your app's jwt_secret. Backed by a revocable session. |
| SSO session | Global session in the HttpOnly+Partitioned cookie al_sso on applogin.one. Shared within one top-level site. |
Quick start
Include two scripts and mount the widget — it silently tries SSO, shows a "Sign in" button, opens the hosted form in a frame and returns the user.
<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>
That's it. The form, registration, email codes, Google and password reset are all handled by Applogin. The only requirement is to register an app_id and list your allowed_origins (see Creating an application).
?v=1 version in the script URLs prevents the browser from serving a stale cached SDK after updates.The ApploginUI widget recommended
A ready-made "lock" for your app: while signed out it shows a screen with a "Sign in" button; a click opens the hosted form in a modal; after sign-in it calls onAuth(user) and removes the screen.
| Method | Description |
|---|---|
ApploginUI.mount({ appId, onAuth, onLogout, title? }) | Mount. Tries silent SSO; if signed out, renders the "Sign in" landing. |
ApploginUI.logout() | Sign out (revokes the session, shows the landing again). |
ApploginUI.getUser() | The current user or null. |
You render the profile (avatar, name, email) and the sign-out button yourself from onAuth(user) — that is your UI. The user data arrives ready to use.
SDK methods (applogin.js) low level
If you want your own UX instead of the widget, use the SDK directly. Call Applogin.init({ appId }) first. The token is stored in localStorage under al_token_<appId>.
| Method | Description | Returns |
|---|---|---|
init({ appId }) | Initialization. | — |
getSession() | Silent SSO: pick up the session if the user signed in to another app of the same site. | { authenticated, token, user } |
openLogin() | Open the hosted login form modal (iframe on applogin.one). | { token, user, lang } or null |
verify() | Check the current token. | { valid, user } |
logout() | Sign out (revokes the session and clears the token). | — |
getToken() | The current app token. | 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
Single sign-on (SSO)
On sign-in Applogin sets the al_sso cookie with the Partitioned attribute. The partition key is the top-level site (the outermost one). Therefore:
- Several of your apps under one site (e.g. embedded into
appdock.pro) share the session →getSession()in the second app picks the sign-in up without a form. - The same app opened as a separate site (another top-level) is a different partition → the user signs in again.
const s = await Applogin.getSession(); // ← {authenticated:true,…} if a session exists in this partition
if (s.authenticated) showApp(s.user);
allowed_origins must list both its own domain and the wrapper domain (e.g. appdock.pro).allowed_origins (a foreign site never gets a token).Languages
The hosted login form speaks 10 languages: English, Deutsch, Français, Español, Italiano, Português, 日本語, 한국어, 中文, Русский. By default it opens in the user's browser language (falling back to English), and the user can switch the language right in the form — the flag menu at the bottom.
- Forcing a language. Pass
langto the widget or the SDK:ApploginUI.mount({ appId, lang: "de" })orApplogin.init({ appId, lang: "de" })— the form opens in that language. Without the option the SDK sends the browser language automatically. - Reading the choice back. The user may switch the language inside the form. The result of
Applogin.openLogin()containslang— the language the form was in at sign-in — so your page can follow it. The widget also persists it (localStorageal_lang), and the next form opens in it. - Emails follow the form. Verification and password-reset emails are sent in the form's language.
Under the hood the form URL accepts ?lang=xx (a two-letter code from the list above) — the SDK passes it for you.
Live example: AppDock
appdock.pro is a "dock" launcher: one page that opens several applications in iframes. The applications live on their own domains:
| Application | Domain | app_id |
|---|---|---|
| Email Writer | emailwriter.online | app_emailwriter |
| Audio Recorder | audiorecorder.info | app_recorder |
Sign in to Email Writer inside the dock, then open Audio Recorder (also inside the dock) — the sign-in is picked up automatically: both run under the same top-level site appdock.pro, so they share one SSO partition.
1. The dock launcher (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. An application (e.g. 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. App registration for this scenario
Each application lists its own domain + the dock domain in allowed_origins (frame-ancestors sees the nested frame's whole ancestor chain):
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"]
ApploginUI.mount.Verifying the token on your backend
Your frontend puts Applogin.getToken() in the Authorization header of its requests. On the backend there are two ways to verify it.
1. Via the platform endpoint revocation-aware
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. Locally (HS256 with your jwt_secret) no network
// 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 structure
{
"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 (optional)
If an application sets a webhook_url, the platform sends it a signed POST with the verified identity on each successful Google sign-in — for server-side syncing in addition to the token.
| Header | Value |
|---|---|
X-Applogin-Timestamp | unix-ms |
X-Applogin-Signature | sha256=<hex> of timestamp + "." + rawBody |
const exp = "sha256=" + crypto.createHmac("sha256", WEBHOOK_SIGNING_SECRET)
.update(ts + "." + rawBody).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(exp), Buffer.from(sig))) reject();
Creating an application
Applications are created in the closed dashboard at applogin.one/dashboard (sign in via Applogin itself). You provide:
| Field | Why |
|---|---|
name | Shown in the login form ("Sign in · <name>"). |
allowed_origins | Origins allowed to embed the form and receive tokens. List the app's domain + the wrapper domain (e.g. the dock). |
redirect_uris | For Google sign-in: https://applogin.one/auth-callback.html. |
webhook_url | (optional) where to send the sign-in webhook. |
You get back: app_id (public), jwt_secret (backend token verification), client_secret, webhook_signing_secret.
REST API — reference
Base: https://applogin.one/api. Below is what a builder actually needs. The email/password and Google-exchange endpoints are called by the hosted form itself — you never call them.
For the builder
Verify an app token on your backend (revocation-aware). { app_id, token } → { valid, user }.
Public application data (name, allowed_origins). Used by the hosted login page.
Internal (called by the hosted form/SDK, not by the builder)
| Endpoint | Role |
|---|---|
POST /v1/email/register · verify · resend · login · reset/request · reset/confirm · password/change | email/password — called from the hosted /login form. |
POST /v1/auth/start, GET /oauth/google/callback, POST /v1/auth/exchange | Google OAuth (inside the hosted form). |
POST /v1/session/sso | Silent SSO (called by /session from a hidden iframe). |
POST /v1/session/establish | Sets the SSO cookie in the correct partition (after Google). |
POST /v1/session/logout | Session revocation (via SDK logout()). |
Errors
| HTTP | When |
|---|---|
| 400 | Bad input (short password, invalid/expired code, "Email already registered"). |
| 403 | "Invalid credentials", access denied, "Too many attempts" (rate limit). |
| 404 | Unknown application/route. |
{"error":"Invalid or expired code","message":"Invalid or expired code"}
Security
- The password is isolated. Input fields exist only inside the applogin.one iframe; the embedding site's JS cannot reach them. A builder cannot "sniff" the password.
- App-scoped tokens. The JWT is signed with the application's secret and carries
aud=app_id— one app's token is invalid in another. A builder cannot enter someone else's app as their user. - Token delivery only to your origins. The form posts the token via
postMessageonly to an origin fromallowed_origins; the login page itself is served withContent-Security-Policy: frame-ancestors <allowed_origins>(anti-clickjacking). - The global session is HttpOnly. The
al_ssocookie is unreachable from JS; the builder only ever holds an app token. - Anti-CSRF on SSO.
/v1/session/ssoissues a token only forOrigin ∈ allowed_origins— a foreign site with the user's cookie gets nothing. - Anti-bruteforce. Rate limits on login/reset/verify (per email and per IP).
- Passwords are hashed (PBKDF2); codes are hashed with a 10-minute TTL and an attempt limit; HTTPS everywhere; Google tokens are never stored. Google sign-in uses PKCE (S256) + state + nonce.
LLM cheat sheet
Model: all authentication happens in an iframe on applogin.one. The builder does NOT render a form, does NOT send passwords, does NOT verify codes. The builder includes the SDK + widget and receives an app token.
// 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: signing in sets the al_sso cookie (Partitioned, per top-level site). Apps under one site (e.g. iframes of one dock like appdock.pro) share the session; a separate domain → sign in again. An app's allowed_origins = its domain + the wrapper domain.
The token is app-scoped: iss=applogin, aud=app_id, sub=user id, signed with the app's jwt_secret.
Applogin · unified sign-in · dashboard · live example: AppDock