Applogin — 통합 로그인
스크립트 하나로 앱에 Google 및 이메일/비밀번호 로그인을 추가하세요. 모든 인증 — 로그인, 가입, 이메일 확인, 비밀번호 재설정, Google — 은 applogin.one의 보호된 iframe 안에서 이루어집니다. 여러분의 페이지는 사용자의 비밀번호를 전혀 볼 수 없고 아무것도 검증하지 않습니다. 받는 것은 바로 사용할 수 있는 앱 전용 토큰뿐입니다.
- 로그인/가입 폼 렌더링(자체 iframe 안에서) — 직접 만들 필요가 없습니다.
- 비밀번호 수신, 자격 증명 확인, 이메일 코드 발송과 검증 — 여러분은 아무것도 검증하지 않습니다.
- 이메일당 하나의 계정 유지(Google과 비밀번호는 같은 계정에 연결).
- 앱 간 세션을 유지하고 여러분의 앱 사이에서 로그인을 자동으로 이어받습니다.
작동 방식
Applogin은 인증 제공자입니다(Auth0/Clerk처럼). 다만 로그인 폼은 우리 도메인의 프레임에서 제공됩니다. 여러분의 앱은 SDK로 그 프레임을 모달로 열고 결과를 postMessage로 받습니다. 토큰은 여러분 애플리케이션의 시크릿으로 서명된 JWT(aud=app_id)라서 다른 애플리케이션에서는 쓸 수 없습니다.
| 개념 | 설명 |
|---|---|
| 애플리케이션(app) | 플랫폼의 엔티티. 공개 app_id, jwt_secret(백엔드 토큰 검증), allowed_origins / redirect_uris 목록을 가집니다. |
| 엔드 유저 | 플랫폼의 모든 앱이 공유하는 글로벌 계정(이메일당 하나). Google과 비밀번호는 같은 계정에 연결됩니다. |
| 앱 토큰 | 앱의 jwt_secret으로 서명된 JWT(HS256, aud=app_id). 취소 가능한 세션이 뒷받침합니다. |
| SSO 세션 | applogin.one의 HttpOnly+Partitioned 쿠키 al_sso에 담긴 글로벌 세션. 같은 최상위 사이트 안에서 공유됩니다. |
빠른 시작
스크립트 두 개를 추가하고 위젯을 마운트하세요. 조용히 SSO를 시도하고, "로그인" 버튼을 표시하고, 호스티드 폼을 프레임으로 열어 사용자를 돌려줍니다.
<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 설정입니다(앱 만들기 참조).
?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 })를 호출합니다. 토큰은 localStorage의 al_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 속성이 있는 쿠키 al_sso를 설정합니다. 파티션 키는 최상위 사이트(가장 바깥쪽)입니다. 따라서:
- 같은 사이트 아래의 여러 앱(예:
appdock.pro에 삽입)은 세션을 공유 → 두 번째 앱의getSession()이 폼 없이 로그인을 이어받습니다. - 같은 앱을 별도 사이트로 열면(다른 최상위) 다른 파티션 → 사용자는 다시 로그인합니다.
const s = await Applogin.getSession(); // ← {authenticated:true,…} if a session exists in this partition
if (s.authenticated) showApp(s.user);
allowed_origins에는 자체 도메인과 래퍼 도메인(예: appdock.pro) 모두 있어야 합니다.allowed_origins로 게이트됩니다(무관한 사이트는 토큰을 얻지 못합니다).언어
호스티드 로그인 폼은 10개 언어를 지원합니다: English, Deutsch, Français, Español, Italiano, Português, 日本語, 한국어, 中文, Русский. 기본적으로 사용자의 브라우저 언어로 열리며(영어 폴백), 폼 안에서 바로 언어를 바꿀 수 있습니다 — 하단의 국기 메뉴입니다.
- 언어 지정. 위젯 또는 SDK에
lang을 전달하세요:ApploginUI.mount({ appId, lang: "ko" })또는Applogin.init({ appId, lang: "ko" })— 폼이 해당 언어로 열립니다. 옵션이 없으면 SDK가 브라우저 언어를 자동으로 보냅니다. - 선택 읽어오기. 사용자는 폼 안에서 언어를 바꿀 수 있습니다.
Applogin.openLogin()의 결과에는 로그인 시점의 폼 언어인lang이 포함되어 페이지가 따라갈 수 있습니다. 위젯은 localStorageal_lang에도 저장하며, 다음 폼은 그 언어로 열립니다. - 이메일은 폼을 따릅니다. 확인 코드와 비밀번호 재설정 메일은 폼의 언어로 발송됩니다.
내부적으로 폼 URL은 ?lang=xx(위 목록의 두 글자 코드)를 받습니다 — SDK가 대신 전달합니다.
라이브 예시: AppDock
appdock.pro는 "독" 런처입니다: 한 페이지에서 여러 앱을 iframe으로 엽니다. 앱들은 각자 자신의 도메인에 있습니다:
| 애플리케이션 | 도메인 | app_id |
|---|---|---|
| Email Writer | emailwriter.online | app_emailwriter |
| Audio Recorder | audiorecorder.info | app_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는 중첩 프레임의 조상 체인 전체를 봅니다):
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만 있습니다.백엔드에서 토큰 검증
프런트엔드는 요청의 Authorization 헤더에 Applogin.getToken()을 넣습니다. 백엔드 검증 방법은 두 가지입니다.
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_url을 설정하면, Google 로그인이 성공할 때마다 검증된 신원이 담긴 서명된 POST가 그곳으로 전송됩니다 — 토큰에 더해 서버 측 동기화를 위해서입니다.
| 헤더 | 값 |
|---|---|
X-Applogin-Timestamp | unix-ms |
X-Applogin-Signature | timestamp + "." + rawBody의 sha256=<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 | (선택) 로그인 웹훅을 보낼 곳. |
받는 것: app_id(공개), jwt_secret(백엔드 토큰 검증), client_secret, webhook_signing_secret.
REST API — 레퍼런스
베이스: https://applogin.one/api. 아래는 개발자에게 실제로 필요한 것입니다. 이메일/비밀번호와 Google 교환 엔드포인트는 호스티드 폼이 직접 호출하며, 여러분은 호출하지 않습니다.
개발자용
백엔드에서 앱 토큰 검증(취소 반영). { app_id, token } → { valid, user }.
앱의 공개 데이터(name, allowed_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/exchange | Google OAuth(호스티드 폼 내부). |
POST /v1/session/sso | 조용한 SSO(숨겨진 iframe에서 /session이 호출). |
POST /v1/session/establish | 올바른 파티션에 SSO 쿠키 설정(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"}
보안
- 비밀번호는 격리됩니다. 입력 필드는 applogin.one의 iframe 안에만 존재하며, 삽입한 사이트의 JS는 접근할 수 없습니다.
- 앱 전용 토큰. JWT는 앱의 시크릿으로 서명되고
aud=app_id를 가집니다 — 한 앱의 토큰은 다른 앱에서 무효입니다. - 토큰은 여러분의 오리진에만 전달. 폼은
allowed_origins의 오리진에만postMessage로 토큰을 보내고, 로그인 페이지 자체는Content-Security-Policy: frame-ancestors <allowed_origins>와 함께 제공됩니다(클릭재킹 방지). - 글로벌 세션은 HttpOnly. 쿠키
al_sso는 JS로 접근 불가. 개발자 손에는 앱 토큰만 있습니다. - SSO의 CSRF 방지.
/v1/session/sso는Origin ∈ allowed_origins일 때만 토큰을 발급 — 사용자의 쿠키를 가진 무관한 사이트는 아무것도 얻지 못합니다. - 브루트포스 방지. login/reset/verify에 레이트 리밋(이메일·IP 단위).
- 비밀번호는 해시 저장(PBKDF2); 코드는 해시·TTL 10분·시도 제한; 모든 곳에 HTTPS; Google 토큰은 저장하지 않음. Google 로그인은 PKCE(S256) + state + nonce.
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: 로그인은 쿠키 al_sso(Partitioned, 최상위 사이트 단위)를 설정한다. 같은 사이트 아래의 앱들(예: appdock.pro 같은 독의 iframe)은 세션을 공유; 별도 도메인 → 재로그인. 앱의 allowed_origins = 자체 도메인 + 래퍼 도메인.
토큰은 앱 전용: iss=applogin, aud=app_id, sub=사용자 ID, 앱의 jwt_secret으로 서명.