Виджет

Хуудсандаа нэг товч нэмнэ. Хэрэглэгч дугаараа оруулаад кодоо тэр утаснаасаа SMS-ээр илгээнэ. Сервер тань нэг дуудлагаар баталгаажсан дугаарыг авна.

Виджет таны хуудсанд "Утасны дугаараар нэвтрэх" товч гаргана. Хэрэглэгч дармагц хуудасны голд цонх нээгдэж, ард тал нь бүдгэрнэ. Эхлээд хэрэглэгч утасны дугаараа оруулна. Дараа нь цонхонд код, 150₮ үнэ гарна. Кодын хажууд хуулах товч байна. Утсан дээр "SMS илгээх" товч, компьютер дээр QR код, доор нь "SMS апп нээх" товч харагдана. Хэрэглэгч кодоо тэр дугаараасаа илгээмэгц цонх баталгаажсан төлөвт шилжээд өөрөө хаагдана.

Нэвтрэх

Example сайтад утасны дугаараараа нэвтэрнэ.

Жишээ. Энд SMS илгээгдэхгүй, сервер рүү хүсэлт явахгүй.

Цонх verify.2fa.mn-ээс ачаалагддаг тул таны хуудас доторх агуулгыг нь уншиж, өөрчилж чадахгүй. Тиймээс үнийг товчоос өмнө харуулах, баталгаажмагц товчийг нуух дүрэм үргэлж биелнэ. Төлөв серверээс шууд (server push, SSE) ирдэг тул та хүлээх, дахин шалгах код бичихгүй.

Баталгаажмагц виджет таны хуудсанд богино настай token өгнө. Та түүнийг сервер дээрээ нэг дуудлагаар солиод баталгаажсан дугаарыг авна. Webhook бичих шаардлагагүй.

API түлхүүр авч, сайтаа нэмэх

Хянах самбар-ын Виджет хэсэгт pk_test_ угтвартай нийтийн API түлхүүр үүсгэнэ. Виджет ажиллах сайтын хаягийг (origin) жагсаалтад нэмнэ. Жишээ нь https://example.mn, хөгжүүлэлтэд http://localhost:3000. localhost, 127.0.0.1 хаягийг зөвхөн тест API түлхүүрт нэмж болно.

Кодоо хуудсандаа тавих

Товч гарах газартаа дараах кодыг тавина.

<div id="twofa"></div>
<script src="https://verify.2fa.mn/embed/v1/embed.js"></script>
<script>
  TwoFA.button('#twofa', { key: 'pk_test_...', state: '<session-д хадгалсан санамсаргүй утга>' })
    .on('verified', ({ token }) => fetch('/login', { method: 'POST', body: token }));
</script>

state-ийг сервер тань нэвтрэх оролдлого бүрт шинээр үүсгэж, тэр хэрэглэгчийн session-д хадгална. Жишээ нь Node.js-д crypto.randomBytes(16).toString('base64url'), Python-д secrets.token_urlsafe(16), PHP-д bin2hex(random_bytes(16)). Яагаад хэрэгтэйг доор тайлбарлав.

verified үйл явдалд ирэх token-ийг өөрийн сервер рүү илгээнэ.

Сервер дээрээ token-оо солиж, state-ээ шалгах

Нууц API түлхүүрээрээ (sk_test_...) POST /v1/verifications хүсэлтийг {"token": "..."} JSON-той нэг удаа илгээнэ. Token нэг л удаа ашиглагдана. Хуучин GET /v1/verifications/{token} хэлбэр ч ажилласаар байгаа ч token URL-д орж log-д үлдэж болзошгүй тул POST-ийг ашиглана уу. Хариунд ирсэн state-ийг session-д хадгалсан утгатай заавал харьцуулна. Таарахгүй бол нэвтрүүлэхгүй.

curl https://api.2fa.mn/v1/verifications \
  -H "Authorization: Bearer $TWOFA_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"token\": \"$TOKEN\"}"
server.js
const res = await fetch('https://api.2fa.mn/v1/verifications', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TWOFA_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ token }),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const { phone, state } = await res.json();
if (state !== req.session.twofaState) throw new Error('state_mismatch');
// phone баталгаажсан. Хэрэглэгчийг нэвтрүүлнэ.
server.py
import hmac, os, requests

res = requests.post(
    "https://api.2fa.mn/v1/verifications",
    headers={"Authorization": f"Bearer {os.environ['TWOFA_KEY']}"},
    json={"token": token},
    timeout=10,
)
res.raise_for_status()
data = res.json()
if not hmac.compare_digest(data["state"], session["twofa_state"]):
    abort(403)
# data["phone"] баталгаажсан. Хэрэглэгчийг нэвтрүүлнэ.
server.php
$ch = curl_init("https://api.2fa.mn/v1/verifications");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("TWOFA_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode(["token" => $token]),
    CURLOPT_TIMEOUT => 10,
]);
$data = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) {
    throw new Exception($data["error"]["code"]);
}
if (!hash_equals($_SESSION["twofa_state"], $data["state"] ?? "")) {
    http_response_code(403);
    exit;
}
// $data["phone"] баталгаажсан. Хэрэглэгчийг нэвтрүүлнэ.

Амжилттай бол 200:

{
  "session_id": "ses_8f3k2m",
  "phone": "99112233",
  "verified_at": "2026-12-01T10:02:14Z",
  "mode": "test",
  "state": "q9Xh2LwP0aT7bV4e"
}

Алдаа гарвал:

HTTPcodeУтга
400invalid_tokenToken буруу эсвэл танигдсангүй
404not_foundToken өөр харилцагч эсвэл өөр горимынх (тест/бодит)
409token_usedToken өмнө нь ашиглагдсан
410token_expiredToken-ий хугацаа дууссан

Бодит орчинд гаргах

Бодит горимд pk_live_ нийтийн API түлхүүр үүсгэж, бодит сайтаа нэмнэ. Бодит API түлхүүрт localhost, 127.0.0.1 нэмэх боломжгүй: хөгжүүлэлтэд тест API түлхүүрээ ашиглана. Сервер дээрээ sk_live_ нууц API түлхүүр ашиглана. Тест горимд бодит SMS илгээгдэхгүй. SMS-ийг хэрхэн дуурайхыг Тест горим хуудаснаас үзнэ үү.

Хоёр хаяг: api.2fa.mn ба verify.2fa.mn

Таны хуудас виджетийг verify.2fa.mn-ээс ачаална. Таны сервер нууц API түлхүүрээрээ api.2fa.mn руу хандаж token-оо солино. Хоёулаа нэг үйлчилгээ. Виджет өөрийн хаягтай тул тусгаарлагдсан орчинд, өөрийн хатуу аюулгүй байдлын бодлоготой (CSP) ажиллаж, нууц API түлхүүрийн API-д хэзээ ч хүрэхгүй.

Хуудсандаа CSP тавьсан бол script-src, frame-src-д https://verify.2fa.mn-ийг нэмнэ. Сервер тань гадагш хандалтыг хязгаарладаг бол api.2fa.mn-ийг зөвшөөрнө.

Яагаад SMS илгээдэг вэ?

Хэрэглэгч дугаараа эхний алхамд оруулдаг ч оруулсан дугаар нь тэр хүнийх гэдгийг батлахгүй. Оператор SMS-ийг илгээгчийн дугаартай нь хамт 150073 руу хүргэдэг. Тиймээс кодыг тэр утаснаас илгээх нь SIM карт хэрэглэгчийн гарт байгааг батална. Өөр дугаараас ирсэн SMS-ийг хүлээн авахгүй: виджет "Өөр дугаараас ирлээ" гэж хэлээд зөв дугаараас илгээхийг сануулна.

Хэрэглэгчийн дугаар танд аль хэдийн мэдэгдэж байгаа бол (жишээ нь бүртгэлд нь хадгалсан) phone тохиргоогоор дамжуулна. Тэгвэл дугаар оруулах алхам алгасагдаж, хэрэглэгч шууд кодоо харна.

State яагаад хэрэгтэй вэ?

state нь token-ийг яг тухайн хөтчийн session-той холбоно. Түүнгүйгээр нэг хөтчид авсан token-ийг өөр хүний нэвтрэх хүсэлтэд оруулж, тэр хүнийг халдагчийн дугаараар нэвтрүүлэх боломжтой. state token дотор бичигдэж, POST /v1/verifications-ийн хариунд буцаж ирдэг. Та түүнийг session-д хадгалсан утгатай харьцуулснаар token яг энэ хөтчөөс эхэлсэн гэдгийг баталгаажуулна.

  • 1-128 тэмдэгт, зөвхөн A-Z a-z 0-9 . _ ~ -. Буруу бол виджет invalid_request алдаа өгнө.
  • Нэвтрэх оролдлого бүрт шинээр үүсгэнэ.
  • Session-д хадгалсан утгатай таарахгүй бол нэвтрүүлэхгүй.

Тохиргоо ба үйл явдал

TwoFA.button(target, options) нь заасан элемент дотор товч гаргаж, удирдах объект буцаана. Session зөвхөн товч дарахад үүснэ.

НэрТайлбар
targetCSS селектор эсвэл элемент, жишээ нь '#twofa'
options.keyНийтийн API түлхүүр (pk_test_... эсвэл pk_live_...)
options.stateТаны session-ий санамсаргүй утга. Token-д холбогдож, redeem-ийн хариунд буцна. Үргэлж дамжуулахыг зөвлөнө
options.phoneМэдэгдэж буй 8 оронтой дугаар, жишээ нь '99112233'. Дугаар оруулах алхмыг алгасаж, зөвхөн тэр дугаараас ирсэн SMS-ийг хүлээн авна. Буруу бол invalid_phone
options.langХэл: mn эсвэл en. Үндсэн нь mn
options.accentТовчийн өнгө, зөвхөн '#rrggbb' хэлбэр. Өөр утга өгвөл #1d3fc4 болно
options.radiusБулангийн радиус, пикселээр, 0-24. Үндсэн нь 12
options.sizeТовчийн хэлбэр: 'full' (бүтэн, үндсэн) эсвэл 'compact' (жижиг)
.on('open', fn)Цонх нээгдлээ
.on('close', fn)Цонх хаагдлаа
.on('verified', fn)Дугаар баталгаажлаа. fn({ token })
.on('expired', fn)Кодын хугацаа дууслаа. Хэрэглэгч цонхноос шинэ код авч болно
.on('error', fn)Алдаа гарлаа. fn алдааны кодыг тэмдэгт мөрөөр авна, жишээ нь 'origin_not_allowed'
.open(), .close()Цонхыг кодоос нээх, хаах
.destroy()Товч, цонхыг хуудаснаас авна

Цонхыг Esc, хаах товч эсвэл ард талыг дарж хаана. Цонх нээлттэй үед хуудас гүйлгэгдэхгүй, хаагдахад фокус товч руу буцна.

Хуудсанд шууд суулгах

Товч, цонхгүйгээр виджетийг хуудсандаа шууд харуулах бол TwoFA.mount-ийг ашиглана. key, lang, phone, state тохиргоо, verified, expired, error үйл явдал, redeem дуудлага ижил. Нэмээд виджет анх харагдахад ready үйл явдал ирнэ. .open(), .close(), accent, radius, size энд байхгүй.

TwoFA.mount('#twofa', { key: 'pk_test_...', state: '...' })
  .on('verified', ({ token }) => fetch('/login', { method: 'POST', body: token }));

Хянах самбараас тохируулах

Хянах самбар-ын Виджет хэсэгт горим (тест/бодит) бүрт тусдаа:

  • Нийтийн API түлхүүр, зөвшөөрсөн сайтууд
  • Загвар (цайвар, бараан, автомат), үндсэн өнгө, булангийн радиус, хэл
  • Товчийн хэлбэр: бүтэн эсвэл жижиг
  • Харагдах нэр: цонхны толгойд {нэр}-д нэвтрэх гэж гарна
  • Хадгалахаас өмнө өөрчлөлтөө шууд харуулах жишээ, тест горимын API түлхүүрээр жинхэнэ виджетийг турших товч
  • Ирж буй баталгаажуулалтын шууд жагсаалт

Мөн виджетийг түр унтрааж болно.

Аюулгүй байдал

  • Нийтийн API түлхүүрийг хуудсанд ил тавихаар зохиосон. Энэ нь зөвхөн виджетийн session эхлүүлнэ, /v1 API-г дуудаж чадахгүй.
  • Зөвхөн жагсаалтад нэмсэн сайтууд дээр виджет ажиллана. Өөр сайт дээр алдааны мессеж харуулна.
  • Token нэг удаагийн, богино настай. Үндсэн хугацаа 10 минут бөгөөд энэ нь тохиргоо.
  • Token-ийг үргэлж өөрийн сервер дээрээ солиж, state-ийг шалгана. Хөтөч дээрх token-д, эсвэл хөтчөөс ирсэн "баталгаажсан" гэсэн мэдээлэлд хэзээ ч бүү итгэ.
  • Нууц API түлхүүр (sk_...) хөтөч рүү хэзээ ч очихгүй.

Офлайн шалгах (JWKS)

Token бол ES256 JWT. Хүсвэл дуудлага хийлгүй, JWKS-ийн public key-ээр шалгаж болно: https://api.2fa.mn/.well-known/jwks.json. Тест горимын token-ийг тусдаа тест signing key-ээр гаргадаг бөгөөд JWKS-д тест, бодит хоёр public key хоёулаа байна. Тиймээс signature таарсан нь бодит token гэсэн үг биш. Дараахыг бүгдийг нь заавал шалгана.

  • Signature нь JWKS дахь public key-тэй (ES256) таарна
  • iss нь яг https://api.2fa.mn. Тест token-д https://api.2fa.mn/test байдаг
  • aud нь яг таны client id. Тест token-д <client id>:test байдаг
  • mode нь live
  • exp нь хараахан өнгөрөөгүй
  • jti нь нэг удаагийнх. Үүнийг өөрөө хадгалж давхардлыг хаана

Офлайн шалгалт нь token-ийг "ашигласан" гэж тэмдэглэхгүй. Тиймээс энгийн тохиолдолд дээрх нэг дуудлагыг ашигла.

Webhook

verification.succeeded webhook виджетийн баталгаажуулалтад ч ирнэ. Үйл явдлын өгөгдөлд "source":"widget" талбар нэмэгдсэн байна. Webhook-оо Webhook ба signature хуудасны дагуу тохируулсан бол өөрчлөлт шаардлагагүй.

Дараагийн алхам

Энэ хуудас тус болсон уу?