Web Loyihalar Integratsiyasi (React, Next.js, Node.js)
OAuth 2.0 + OpenID Connect PKCE (S256) Web Auth Protocol
Ushbu qo'llanma orqali har qanday Web loyihaga (React, Next.js, Vue, Angular, Node.js, PHP) BioID biometrik 2FA Single Sign-On tizimini integratsiya qilishingiz mumkin.
1. Web OAuth 2.0 Ketma-ketligi
[ Web App Client ] ── 1. PKCE Code Challenge (S256) ──► [ BioID Login Sahifasi ]
│ │
│ 2. Fuqaro Biometrik 2FA Skaneri
│ │
├── 4. https://app.uz/auth/callback?code=... ◄───────────┘
│ (Redirect URI Callback)
│
├── 5. POST /api/oauth/token (code + code_verifier) ────► [ BioID Server ]
│ │
└── 6. GET /api/oauth/userinfo (Bearer Token) ───────────► [ Citizen Profile ]2. OAuth 2.0 API Server Parametrlari
| Endpoint | Method | Manzil | Vazifasi |
|---|---|---|---|
| Authorize URL | GET | /api/oauth/authorize | Avtorizatsiya va yuz skaneri oynasiga yuborish |
| Token URL | POST | /api/oauth/token | Code va PKCE Verifier-ni Access Token-ga almashtirish |
| Userinfo URL | GET | /api/oauth/userinfo | Fuqaroning shaxsiy va biometrik ma'lumotlarini olish |
3. JavaScript / TypeScript SDK Servis Kodu
Ushbu tayyor sinfni loyihangizga joylashtiring (bioid-web-sdk.ts):
// bioid-web-sdk.ts
export interface BioIDUserProfile {
sub: string;
pinfl: string;
name: string;
given_name: string;
family_name: string;
middle_name?: string;
passport_series: string;
passport_number: string;
phone_number: string;
}
export class BioIDWebSDK {
private baseUrl: string;
private clientId: string;
private redirectUri: string;
constructor(config: { baseUrl?: string; clientId: string; redirectUri: string }) {
this.baseUrl = config.baseUrl || 'http://localhost:3000';
this.clientId = config.clientId;
this.redirectUri = config.redirectUri;
}
/**
* SHA-256 Code Challenge Generator (PKCE S256)
*/
private async generatePKCE(): Promise<{ verifier: string; challenge: string }> {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const verifier = Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
const challenge = btoa(String.fromCharCode(...new Uint8Array(hash)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
return { verifier, challenge };
}
/**
* BioID Avtorizatsiya sahifasiga yo'naltirish
*/
public async redirectToLogin(): Promise<void> {
const { verifier, challenge } = await this.generatePKCE();
const state = Math.random().toString(36).substring(2, 15);
// PKCE verifier va state-ni brauzer sessionStorage-da saqlash
sessionStorage.setItem('bioid_code_verifier', verifier);
sessionStorage.setItem('bioid_auth_state', state);
const authUrl = new URL(`${this.baseUrl}/api/oauth/authorize`);
authUrl.searchParams.set('client_id', this.clientId);
authUrl.searchParams.set('redirect_uri', this.redirectUri);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'openid profile pinfl phone');
authUrl.searchParams.set('state', state);
authUrl.searchParams.set('code_challenge', challenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
window.location.href = authUrl.toString();
}
/**
* Callback sahifasida Authorization Code-ni Access Token-ga almashtirish
*/
public async handleCallback(code: string): Promise<BioIDUserProfile> {
const verifier = sessionStorage.getItem('bioid_code_verifier');
if (!verifier) {
throw new Error("PKCE Code Verifier topilmadi. Avtorizatsiya qaytadan boshlanishi kerak.");
}
// 1. Token olish (POST /api/oauth/token)
const tokenRes = await fetch(`${this.baseUrl}/api/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
client_id: this.clientId,
code,
redirect_uri: this.redirectUri,
code_verifier: verifier,
}),
});
const tokenData = await tokenRes.json();
if (!tokenRes.ok || !tokenData.access_token) {
throw new Error(tokenData.error || "Access Token olishda xatolik yuz berdi");
}
// PKCE seansini tozalash
sessionStorage.removeItem('bioid_code_verifier');
sessionStorage.removeItem('bioid_auth_state');
// 2. Fuqaro ma'lumotlarini olish (GET /api/oauth/userinfo)
const userinfoRes = await fetch(`${this.baseUrl}/api/oauth/userinfo`, {
headers: {
Authorization: `Bearer ${tokenData.access_token}`,
},
});
if (!userinfoRes.ok) {
throw new Error("Fuqaro profil ma'lumotlarini olishda xatolik");
}
return await userinfoRes.json();
}
}4. React / Next.js Sahifa va Tugma Misoli
import React, { useEffect, useState } from 'react';
import { BioIDWebSDK, BioIDUserProfile } from './bioid-web-sdk';
const bioid = new BioIDWebSDK({
clientId: 'YOUR_BIOID_CLIENT_ID',
redirectUri: 'http://localhost:3000/auth/callback',
});
// Kirish Tugmasi Komponenti
export function BioIDLoginButton() {
return (
<button
onClick={() => bioid.redirectToLogin()}
className="px-6 py-3 bg-[#0052FF] text-white font-bold text-sm rounded-2xl shadow-md hover:bg-blue-700 transition-all"
>
BioID Orqali Kirish (2FA Face ID)
</button>
);
}
// Callback Ishlovchi Sahifa Komponenti
export function AuthCallbackPage() {
const [profile, setProfile] = useState<BioIDUserProfile | null>(null);
const [error, setError] = useState('');
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (code) {
bioid
.handleCallback(code)
.then((userData) => setProfile(userData))
.catch((err) => setError(err.message));
} else {
setError("Avtorizatsiya kodi topilmadi");
}
}, []);
if (error) return <div className="text-red-600 font-semibold p-4">Xatolik: {error}</div>;
if (!profile) return <div className="text-blue-600 font-semibold p-4">Biometrik Profil Tasdiqlanmoqda...</div>;
return (
<div className="p-6 bg-white rounded-3xl border border-slate-200 shadow-md space-y-3">
<h3 className="text-lg font-bold text-slate-900">Muvaffaqiyatli Kirildi</h3>
<p><strong>F.I.SH:</strong> {profile.name}</p>
<p><strong>JSHSHIR:</strong> {profile.pinfl}</p>
<p><strong>Pasport:</strong> {profile.passport_series} {profile.passport_number}</p>
<p><strong>Tel:</strong> {profile.phone_number}</p>
</div>
);
}