Developer documentation · v1

Integrate your
system with Gerege ID

Web, mobile, desktop, эсвэл X-Road consumer-аас Гэрэгэ ID-ээр login, document signing, KYC хэрхэн нэгтгэх вэ — кодын жишээ, SDK, sandbox test.gerege.mn-тэй.

ТАНИЛЦУУЛГА

Юу хийж болох вэ?

🔑

Login (Web auth)

Хэрэглэгч "Login with Gerege" товч дарна → Гэрэгэ ID мобайл app-руу push notification ирнэ → биометрик баталгаажуулсны дараа таны системд session нээгдэнэ.

✍️

Document signing

Гэрээний PDF/JSON-ийг hash хийж sign session эхлүүлнэ. Signed XAdES/JAdES баталгаа TSA-аар timestamp хийгдэн legally binding нотолгоо болно.

🪪

KYC + verified attributes

Иргэний бүртгэл дээр баталгаажсан овог, нэр, төрсөн он, регистр, иргэний харьяалал-уудыг login дараа JWT доторх claim-аар хүлээж авна.

АРХИТЕКТУР

Холболтын ерөнхий схем

Web appReact, Vue, Next.js Mobile appiOS Swift, Android Kotlin Desktop appElectron, .NET, JavaFX Your backend(Go / Node / Python …) REST X-Road SS(консумер gateway) X-Road Gerege IDbackend App → Backend → X-Road consumer SS → Gerege ID producer SS → Gerege backend

2 түвшний security

Танай app-аас backend руу: standard OAuth/session. Backend-аас Гэрэгэ-руу: X-Road mTLS + signed message. End-to-end identity baталгаа.

Direct REST option

X-Road consumer SS суулгахгүй бол backend нь шууд https://ca.gerege.mn/rp/v1/* RP API руу хандах боломжтой (API key + IP whitelist). MVP-д тохиромжтой.

WEB INTEGRATION

Web app холбох

Login button → backend session initiate → poll → callback. Frontend нь зөвхөн UI, бүх crypto handshake backend дээр.

FRONTEND (Next.js / React)
login-button.tsx
async function loginWithGerege() {
  // 1. Backend-руу session эхлүүлэх хүсэлт
  const init = await fetch("/api/auth/initiate", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      national_id: prompt("Регистрийн дугаар:"),
      display_text: "MyApp руу нэвтрэх",
    }),
  }).then(r => r.json());

  setStatus(`Гэрэгэ ID app-аас баталгаажуулна уу. PIN: ${init.pin}`);

  // 2. Status polling — Гэрэгэ ID mobile-аас confirm/refuse хүртэл
  const pollId = setInterval(async () => {
    const s = await fetch(`/api/auth/status/${init.session_id}`).then(r => r.json());
    if (s.status === "CONFIRMED") {
      clearInterval(pollId);
      window.location.href = "/dashboard"; // session cookie set
    } else if (s.status === "REFUSED" || s.status === "EXPIRED") {
      clearInterval(pollId);
      setStatus(`Цуцалгдсан: ${s.status}`);
    }
  }, 1500);
}
BACKEND (Go)
handlers/auth.go
// POST /api/auth/initiate
func handleInitiate(w http.ResponseWriter, r *http.Request) {
    var req struct {
        NationalID  string `json:"national_id"`
        DisplayText string `json:"display_text"`
    }
    json.NewDecoder(r.Body).Decode(&req)

    body, _ := json.Marshal(map[string]string{
        "national_id":  req.NationalID,
        "display_text": req.DisplayText,
    })
    apiReq, _ := http.NewRequest("POST",
        "https://ca.gerege.mn/rp/v1/auth/initiate",
        bytes.NewReader(body))
    apiReq.Header.Set("Authorization", "Bearer "+os.Getenv("RP_API_KEY"))
    apiReq.Header.Set("Content-Type", "application/json")

    resp, _ := http.DefaultClient.Do(apiReq)
    defer resp.Body.Close()
    io.Copy(w, resp.Body) // {session_id, pin, expires_at}
}

// GET /api/auth/status/{session_id}
func handleStatus(w http.ResponseWriter, r *http.Request) {
    sid := r.PathValue("session_id")
    apiReq, _ := http.NewRequest("GET",
        "https://ca.gerege.mn/rp/v1/auth/session/"+sid, nil)
    apiReq.Header.Set("Authorization", "Bearer "+os.Getenv("RP_API_KEY"))
    resp, _ := http.DefaultClient.Do(apiReq)
    defer resp.Body.Close()
    io.Copy(w, resp.Body) // {status: PENDING|CONFIRMED|REFUSED, user?: {...}}
}
📝 Тэмдэглэл
  • RP_API_KEY — Гэрэгэ-аас өгөгдөнө. Хэзээ ч frontend-руу exposeлохгүй
  • RP-ийн public IP-ийг Гэрэгэ backend admin-д whitelist хийлгэх ёстой
  • Document signing flow ялгаатай: /rp/v1/sign/initiate ашиглана (доор)
MOBILE INTEGRATION

iOS / Android app

Native mobile app дээр universal-link / app-link-аар Гэрэгэ ID app руу шилжүүлж auth flow эхлүүлнэ.

LoginManager.swift
import Foundation

class GeregeLogin {
    let backendURL = "https://api.myapp.mn/auth"

    func login(nationalID: String) async throws -> User {
        // 1. Initiate via backend
        let body = ["national_id": nationalID, "display_text": "MyApp нэвтрэх"]
        var req = URLRequest(url: URL(string: "\(backendURL)/initiate")!)
        req.httpMethod = "POST"
        req.setValue("application/json", forHTTPHeaderField: "Content-Type")
        req.httpBody = try JSONSerialization.data(withJSONObject: body)
        let (data, _) = try await URLSession.shared.data(for: req)
        let init_ = try JSONDecoder().decode(InitResp.self, from: data)

        // 2. Open Gerege ID app via universal link to auto-confirm
        if let url = URL(string: "gerege-id://auth/\(init_.session_id)") {
            await UIApplication.shared.open(url)
        }

        // 3. Poll backend status
        while true {
            try await Task.sleep(nanoseconds: 1_500_000_000)
            let s = try await fetchStatus(init_.session_id)
            if s.status == "CONFIRMED" { return s.user! }
            if s.status == "REFUSED" || s.status == "EXPIRED" {
                throw NSError(domain: "Gerege", code: -1)
            }
        }
    }
}
class GeregeLogin(private val backend: String = "https://api.myapp.mn/auth") {

    suspend fun login(nationalId: String): User {
        val client = OkHttpClient()
        val body = JSONObject().apply {
            put("national_id", nationalId)
            put("display_text", "MyApp нэвтрэх")
        }.toString().toRequestBody("application/json".toMediaType())

        // 1. Initiate
        val initReq = Request.Builder().url("$backend/initiate").post(body).build()
        val initResp = client.newCall(initReq).execute().use {
            JSONObject(it.body!!.string())
        }
        val sessionId = initResp.getString("session_id")

        // 2. Open Gerege ID via app link
        context.startActivity(Intent(Intent.ACTION_VIEW,
            Uri.parse("https://gerege.mn/auth/$sessionId")))

        // 3. Poll
        while (true) {
            delay(1500)
            val s = client.newCall(Request.Builder()
                .url("$backend/status/$sessionId").build()).execute().use {
                    JSONObject(it.body!!.string())
                }
            when (s.getString("status")) {
                "CONFIRMED" -> return User.fromJson(s.getJSONObject("user"))
                "REFUSED", "EXPIRED" -> throw IOException("auth ${s.getString("status")}")
            }
        }
    }
}
import { Linking } from 'react-native';

export async function loginWithGerege(nationalId: string) {
  const init = await fetch('https://api.myapp.mn/auth/initiate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ national_id: nationalId, display_text: 'MyApp' }),
  }).then(r => r.json());

  // Open Gerege ID app
  await Linking.openURL(`gerege-id://auth/${init.session_id}`);

  // Poll until confirmed
  while (true) {
    await new Promise(r => setTimeout(r, 1500));
    const s = await fetch(`https://api.myapp.mn/auth/status/${init.session_id}`)
      .then(r => r.json());
    if (s.status === 'CONFIRMED') return s.user;
    if (s.status === 'REFUSED' || s.status === 'EXPIRED') throw new Error(s.status);
  }
}
DESKTOP INTEGRATION

Desktop app

Electron, Tauri, .NET, эсвэл native app — embedded webview-аар browser flow ашиглах эсвэл system browser нээх.

main.js (Electron)
const { BrowserWindow, shell } = require('electron');

async function loginWithGerege(nationalId) {
  const init = await fetch('https://api.myapp.mn/auth/initiate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ national_id: nationalId,
                          display_text: 'MyDesktopApp' }),
  }).then(r => r.json());

  // Open user's mobile via QR or web fallback
  const qrWin = new BrowserWindow({ width: 400, height: 500 });
  qrWin.loadURL(`https://gerege.mn/qr?session=${init.session_id}`);

  while (true) {
    await new Promise(r => setTimeout(r, 1500));
    const s = await fetch(`https://api.myapp.mn/auth/status/${init.session_id}`)
      .then(r => r.json());
    if (s.status === 'CONFIRMED') {
      qrWin.close();
      return s.user;
    }
  }
}
GeregeAuth.cs (.NET)
public class GeregeAuth {
  private readonly HttpClient _http = new();
  private const string BACKEND = "https://api.myapp.mn/auth";

  public async Task<User> LoginAsync(string nationalId) {
    var init = await _http.PostAsJsonAsync($"{BACKEND}/initiate",
      new { national_id = nationalId, display_text = "MyApp" });
    var initData = await init.Content.ReadFromJsonAsync<InitResp>();

    // Open system browser to QR page
    Process.Start(new ProcessStartInfo {
      FileName = $"https://gerege.mn/qr?session={initData.SessionId}",
      UseShellExecute = true,
    });

    while (true) {
      await Task.Delay(1500);
      var s = await _http.GetFromJsonAsync<StatusResp>(
        $"{BACKEND}/status/{initData.SessionId}");
      if (s.Status == "CONFIRMED") return s.User;
      if (s.Status == "REFUSED" || s.Status == "EXPIRED")
        throw new Exception($"auth {s.Status}");
    }
  }
}
X-ROAD CONSUMER

X-Road-аар хандах (production)

Production-д mTLS + signed message + audit log-той X-Road consumer pattern. RP API key-тай харьцуулахад илүү secure ба audit-ready.

1

SS бүртгүүлэх

Байгууллагаа X-Road MN instance-д member болгож, Security Server суулгана. Дэлгэрэнгүй: x-road.mn

2

Subsystem үүсгэх

SS UI → Clients → Add subsystem (жишээ CLIENT-APP). Энэ subsystem нь consumer identity болно.

3

Гэрэгэ-аас ACL access авах

Гэрэгэ-ийн SS дээрх geregeIdAuth, geregeIdSign service-уудад таны subsystem нэмэхийг хүснэ.

4

Backend-аас SS-руу call

Танай backend нь өөрийн SS-ийн внутр HTTP endpoint http://ss.local:8080-руу REST request явуулна. SS нь X-Road handshake + signing хийж producer руу дамжуулна.

backend/xroad-call.go
// X-Road REST call to Gerege ID auth service
// SS internal endpoint: http://ss.local:8080
func authViaXRoad(nationalID string) (*Session, error) {
    req, _ := http.NewRequest("POST",
        "http://ss.local:8080/r1/MN/COM/6235972/GEREGE-ID/auth/initiate",
        strings.NewReader(`{"national_id":"`+nationalID+`"}`))

    // Required X-Road headers
    req.Header.Set("X-Road-Client",
        "MN/COM/6884857/CLIENT-APP")  // your subsystem
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil { return nil, err }
    defer resp.Body.Close()

    var s Session
    json.NewDecoder(resp.Body).Decode(&s)
    return &s, nil
}

// URL pattern: /r1/{instance}/{class}/{member}/{subsystem}/{service}/{path}
//   instance  = MN
//   class     = COM (commercial) | GOV | NGO
//   member    = 6235972 (Gerege member code)
//   subsystem = GEREGE-ID
//   service   = auth (with subpath /initiate)
⚡ Direct REST vs X-Road
ШинжDirect RESTX-Road
SetupAPI key + IP whitelistSS install + subsystem reg
mTLSOptionalRequired (cert chain)
Message signingNoneXAdES-T per request
Audit logBackend onlySS message log (legally binding)
Use caseMVP / startupProduction / banks / govt

Туршаад үзэх

test.gerege.mn-д бид login + sign flow-ийг бодит ss.gerege.mn ашиглан түүхэн харуулсан. Source code MIT license-тэй.