Tamga nähili işleýär
Tamga bilen gir
“Google bilen gir” sana tanyş. “Tamga bilen gir” şeýle görünýär, ýöne tersine işleýär: şahsyýet seniň gapjygyňda, saýt diňe tassyklan meýdanlaryňy alýar we ilkinji gezekden soň hiç zat almaýar.
Iki ädim: bir gezek hasaba durmak, soň passkey
- Hasaba durmak (bir gezek). Saýt az sanly meýdan soraýar — meselem, ady we familiýasy — we seniň üçin özüne degişli lakamy alýar. Kompýuterde QR kody okadýarsyň; telefonda gapjyk göni açylýar. Gapjyk nämäniň, kim tarapyndan soralýandygyny takyk görkezýär; sen tassyklaýarsyň.
- Barlaýjy hödürlemäni barlaýar — gol, ynam sanawy, ýatyrylyş ýagdaýy we enjam baglanyşygy. Saýt netijäni öz serwerinde alýar we hasabyňy açýar.
- Passkey goş. Hasaba duranyňdan soň saýt “bu enjama passkey goş” diýip teklip edýär. Mundan beýläk Face ID ýa-da barmak yzy bilen girýärsiň: gapjyk açylmaýar, hiç bir meýdan paýlaşylmaýar. Passkey diňe şol saýt üçin işleýär.
- Täze enjam, passkey ýokmy? “Tamga bilen gir” diňe şol saýta degişli lakamyňy iberýär — hiç bir resminama meýdany ýok — soň ýene passkey goşýarsyň.
Web saýt
meýdan soraýar
Tamga Wallet
sen tassyklaýarsyň
Barlaýjy
subutnamany barlaýar
Passkey
gündelik giriş
Meýdan-meýdan sen tassyklaýarsyň
Saýt diňe zerur zady soraýar; tassyklamadyk meýdanyň asla iberilmeýär. Käbir maglumatlar açylman subut edilip bilner — doglan senesi berilmän “18 ýaşdan uly”.
example.com şulara girmek isleýär:
Parol paýlaşylmaýar · her meýdany sen dolandyrýarsyň
Saýt näme alyp biler — näme alyp bilmez
Işläp düzüjiler üçin
Sahypa toplumy @tamga-network/verifier/web (barlaýjy ony skript hökmünde hem hödürleýär). QR kody ýa-da “gapjykda aç” düwmesini çekýär we netijäni habar berýär — karary bermeýär: brauzer kody ulanyjy tarapyndan üýtgedilip bilner, şonuň üçin karar hemişe seniň serweriňde berilýär.
/**
* Example 01 — "Sign up / Sign in with Tamga" on your website, using the hosted verifier.
*
* Your SERVER opens each presentation, proving who it is with a short-lived assertion signed by the key of
* your trust-list registration (ADR-0017). The page only shows the QR code and polls a status token; the
* values the person approved are handed to your server once.
*
* npm install @tamga-network/verifier
*/
import { createHmac } from "node:crypto";
import { createRpAssertion, type RpSigner } from "@tamga-network/verifier";
export interface SignInConfig {
/** The hosted verifier, e.g. "https://verify.tamga.network". */
verifier: string;
/** Your registration: pemRpSigner(KEY_PEM, CERT_PEM) — client_id is x509_hash of your access certificate (HAIP 1.0). */
rp: RpSigner;
/** Your own secret; the stored account key is a keyed hash of the site pseudonym. */
siteSecret: string;
fetch?: typeof fetch;
}
/** What your page needs to draw the QR code and poll the status. */
export interface StartedPresentation {
presentation_id: string;
qr_payload: string;
expires_at: string;
status_token: string;
}
export type SignInResult =
| { ok: true; accountKey: string; givenName?: string; familyName?: string }
| { ok: false; outcome: "PENDING" | "REJECTED" | "INDETERMINATE" | "ALREADY_USED" };
export function tamgaSignIn(cfg: SignInConfig) {
const f = cfg.fetch ?? fetch;
const auth = async () => ({ authorization: `Bearer ${await createRpAssertion(cfg.rp, cfg.verifier)}` });
/** POST /tamga/start → return this JSON to your page (TamgaVerifier.mount({ start })). */
async function start(policy: "site-signup" | "site-signin"): Promise<StartedPresentation> {
const r = await f(`${cfg.verifier}/presentations`, {
method: "POST",
headers: { ...(await auth()), "content-type": "application/json", accept: "application/json" },
body: JSON.stringify({ policy_id: policy }),
});
if (!r.ok) throw new Error(`verifier refused the request (${r.status})`);
return (await r.json()) as StartedPresentation;
}
/** POST /tamga/session → your page sends the presentation id after ACCEPTED; decide here, on the server. */
async function finish(presentationId: string): Promise<SignInResult> {
const id = encodeURIComponent(presentationId);
const result = (await (await f(`${cfg.verifier}/presentations/${id}`, { headers: await auth() })).json()) as {
outcome?: "ACCEPTED" | "REJECTED" | "INDETERMINATE";
};
if (result.outcome !== "ACCEPTED") return { ok: false, outcome: result.outcome ?? "PENDING" };
const r = await f(`${cfg.verifier}/presentations/${id}/claims`, { headers: await auth() });
if (r.status === 410) return { ok: false, outcome: "ALREADY_USED" }; // values are handed out once
const { claims } = (await r.json()) as { claims: Record<string, unknown> };
// ADR-0031: the account key is the wallet's pseudonym for YOUR site (another site sees a different one); the verifier
// has checked its signature, audience, nonce and the wallet instance attestation. No document value is sent.
if (typeof claims.pseudonym !== "string") return { ok: false, outcome: "REJECTED" };
const accountKey = createHmac("sha256", cfg.siteSecret).update(claims.pseudonym).digest("base64url");
return {
ok: true,
accountKey,
givenName: claims.given_name as string | undefined,
familyName: claims.family_name as string | undefined,
};
}
return { start, finish };
}import { passkey } from "@tamga-network/verifier/web";
// after sign-up, while the session is open:
const o = await post("/passkey/register/options");
await post("/passkey/register/verify", await passkey.create(o));
// daily sign-in — no wallet, no fields shared:
const { flow, options } = await post("/passkey/login/options");
await post("/passkey/login/verify", { flow, response: await passkey.get(options) });- Bir gezek ulanmak: bir hödürleme takyk bir sessiýa açýar.
- Üç netije: ACCEPTED, REJECTED ýa-da INDETERMINATE. INDETERMINATE “häzir barlap bolmady — gaýtadan synanyş” diýmekdir, asla “resminama nädogry” däl.
- Kuki: HttpOnly, SameSite=Lax, Secure, serwer tarapynda möhlet; at, açar ýa-da passkey belgisi asla žurnala ýazylmaýar.
- WebAuthn ýalaňaç IP salgysynda işlemeýär —
localhostýa-da HTTPS domen ulan.
Açyk standartlara esaslanýar
Ýapyk giriş ulgamy däl: hödürlemeler SD-JWT VC resminamalary bilen OpenID for Verifiable Presentations (OpenID4VP) ulanýar — ÝB-niň EUDI Wallet-y bilen şol bir profiller — gündelik giriş bolsa standart WebAuthn passkey.
Häzirki ýagdaý