Виджет
Хуудсандаа нэг товч нэмнэ. Хэрэглэгч дугаараа оруулаад кодоо тэр утаснаасаа SMS-ээр илгээнэ. Сервер тань нэг дуудлагаар баталгаажсан дугаарыг авна.
Виджет таны хуудсанд "Утасны дугаараар нэвтрэх" товч гаргана. Хэрэглэгч дармагц хуудасны голд цонх нээгдэж, ард тал нь бүдгэрнэ. Эхлээд хэрэглэгч утасны дугаараа оруулна. Дараа нь цонхонд код, 150₮ үнэ гарна. Кодын хажууд хуулах товч байна. Утсан дээр "SMS илгээх" товч, компьютер дээр QR код, доор нь "SMS апп нээх" товч харагдана. Хэрэглэгч кодоо тэр дугаараасаа илгээмэгц цонх баталгаажсан төлөвт шилжээд өөрөө хаагдана.
Нэвтрэх
Example сайтад утасны дугаараараа нэвтэрнэ.
Цонх 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\"}"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 баталгаажсан. Хэрэглэгчийг нэвтрүүлнэ.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"] баталгаажсан. Хэрэглэгчийг нэвтрүүлнэ.$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"
}Алдаа гарвал:
| HTTP | code | Утга |
|---|---|---|
| 400 | invalid_token | Token буруу эсвэл танигдсангүй |
| 404 | not_found | Token өөр харилцагч эсвэл өөр горимынх (тест/бодит) |
| 409 | token_used | Token өмнө нь ашиглагдсан |
| 410 | token_expired | Token-ий хугацаа дууссан |
Бодит орчинд гаргах
Бодит горимд 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 зөвхөн товч дарахад үүснэ.
| Нэр | Тайлбар |
|---|---|
target | CSS селектор эсвэл элемент, жишээ нь '#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 эхлүүлнэ,
/v1API-г дуудаж чадахгүй. - Зөвхөн жагсаалтад нэмсэн сайтууд дээр виджет ажиллана. Өөр сайт дээр алдааны мессеж харуулна.
- 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ньliveexpнь хараахан өнгөрөөгүйjtiнь нэг удаагийнх. Үүнийг өөрөө хадгалж давхардлыг хаана
Офлайн шалгалт нь token-ийг "ашигласан" гэж тэмдэглэхгүй. Тиймээс энгийн тохиолдолд дээрх нэг дуудлагыг ашигла.
Webhook
verification.succeeded webhook виджетийн баталгаажуулалтад ч ирнэ. Үйл явдлын өгөгдөлд "source":"widget" талбар нэмэгдсэн байна. Webhook-оо Webhook ба signature хуудасны дагуу тохируулсан бол өөрчлөлт шаардлагагүй.
Дараагийн алхам
Энэ хуудас тус болсон уу?