WebAuthn و Passkeys
WebAuthn (Web Authentication API) توفر مصادقة مقاومة للتصيد باستخدام credentials مشفرة مخزنة على جهاز المستخدم أو مفتاح أمان خارجي. هي التقنية وراء Passkeys و Face ID و Touch ID و Windows Hello و YubiKeys.
Ithbat IAM ينفّذ مواصفات FIDO2/WebAuthn بالكامل. الـ Credentials تُخزَّن لكل مستخدم، والمصادقة تستخدم Challenge-Response ceremony ما يمكن التصيد عبرها.
ماذا يوفر WebAuthn
| نوع الـ Authenticator | أمثلة | آلية العمل |
|---|---|---|
| Platform authenticator | Face ID, Touch ID, Windows Hello, Android biometrics | مدمج في الجهاز — الـ Credential لا يغادر الجهاز أبدًا |
| Roaming authenticator | YubiKey 5, Google Titan | مفتاح أمان عبر USB أو NFC أو Bluetooth |
| Passkeys (synced) | iCloud Keychain, Google Password Manager | Platform credential يُزامن عبر الأجهزة بالسحابة |
WebAuthn credentials المسجلة في Ithbat IAM تشتغل كعامل مصادقة أساسي مستقل (بدون كلمة مرور) أو كعامل ثانٍ قوي بعد تسجيل الدخول بكلمة المرور.
توافق المتصفحات
WebAuthn مدعوم في كل المتصفحات الحديثة:
| المتصفح | الحد الأدنى |
|---|---|
| Chrome | 67+ |
| Safari | 14+ |
| Firefox | 60+ |
| Edge | 18+ |
| Samsung Internet | 10.2+ |
Registration Ceremony
المستخدم يجب يكون مصادق (لديه Access Token صالح) لكي يسجّل WebAuthn credential.
sequenceDiagram
participant User
participant App as Your App (Browser)
participant Ithbat as Ithbat IAM
participant Auth as Authenticator (Device / Key)
User->>App: Click "Add passkey"
App->>Ithbat: POST /api/v1/mfa/webauthn/register/start
Ithbat->>Ithbat: Generate registration challenge
Ithbat-->>App: { options: { publicKey: { challenge, rp, user, ... } } }
App->>Auth: navigator.credentials.create(options.publicKey)
Auth-->>User: Biometric prompt / key tap
User->>Auth: Approve
Auth-->>App: PublicKeyCredential (attestation)
App->>Ithbat: POST /api/v1/mfa/webauthn/register/finish (raw credential response)
Ithbat->>Ithbat: Verify attestation, store public key
Ithbat-->>App: { credentialId, message }
الخطوة 1 — بدء التسجيل
POST /api/v1/mfa/webauthn/register/start
Header مطلوب: Authorization: Bearer <accessToken>
{
"credentialName": "My MacBook Pro"
}
credentialName اختياري. إذا أرسلته، يُحفظ كتسمية مقروءة للـ Credential.
الـ Response — 200 OK
{
"options": {
"publicKey": {
"challenge": "dGhpcyBpcyBhIGNoYWxsZW5nZQ==",
"rp": {
"name": "Ithbat IAM",
"id": "api.ithbat.io"
},
"user": {
"id": "dXNlcl9pZA==",
"name": "[email protected]",
"displayName": "Sara Al-Rashidi"
},
"pubKeyCredParams": [
{ "type": "public-key", "alg": -7 },
{ "type": "public-key", "alg": -257 }
],
"timeout": 60000,
"attestation": "none",
"authenticatorSelection": {
"residentKey": "preferred",
"userVerification": "preferred"
}
}
},
"message": "Present your authenticator to register"
}
مرّر كائن options.publicKey كامل لـ WebAuthn API.
الخطوة 2 — إتمام التسجيل
بعد ما المستخدم يوافق على biometric prompt، المتصفح يرجع PublicKeyCredential. أرسل raw credential Response لـ:
POST /api/v1/mfa/webauthn/register/finish
Header مطلوب: Authorization: Bearer <accessToken>
الـ Request body يجب يكون الـ raw PublicKeyCredential JSON من navigator.credentials.create(). الـ Ithbat SDK يتولى هذا تلقائيًا.
الـ Response — 200 OK
{
"credentialId": "dGhpcyBpcyBhIGNyZWRlbnRpYWwgaWQ=",
"message": "WebAuthn credential registered successfully"
}
Authentication Ceremony
تسجيل الدخول عبر WebAuthn ما يتطلب كلمة مرور. المستخدم يعرّف نفسه بالبريد الإلكتروني، والـ Authenticator يثبت حيازة الـ private key.
sequenceDiagram
participant User
participant App as Your App (Browser)
participant Ithbat as Ithbat IAM
participant Auth as Authenticator
User->>App: Enter email
App->>Ithbat: POST /api/v1/mfa/webauthn/login/start { email }
Ithbat->>Ithbat: Lookup credentials for user, generate challenge
Ithbat-->>App: { options: { publicKey: { challenge, allowCredentials, ... } } }
App->>Auth: navigator.credentials.get(options.publicKey)
Auth-->>User: Biometric prompt / key tap
User->>Auth: Approve
Auth-->>App: PublicKeyCredential (assertion)
App->>Ithbat: POST /api/v1/mfa/webauthn/login/finish { email, <credential response> }
Ithbat->>Ithbat: Verify assertion signature against stored public key
Ithbat-->>App: { accessToken, refreshToken, expiresIn }
App-->>User: Signed in
الخطوة 1 — بدء تسجيل الدخول
POST /api/v1/mfa/webauthn/login/start
{
"email": "[email protected]"
}
الـ Response — 200 OK
{
"options": {
"publicKey": {
"challenge": "cmFuZG9tQ2hhbGxlbmdl",
"allowCredentials": [
{
"type": "public-key",
"id": "dGhpcyBpcyBhIGNyZWRlbnRpYWwgaWQ=",
"transports": ["internal", "hybrid"]
}
],
"timeout": 60000,
"userVerification": "preferred"
}
},
"message": "Present your authenticator to sign in"
}
مرّر options.publicKey لـ navigator.credentials.get().
الخطوة 2 — إتمام تسجيل الدخول
POST /api/v1/mfa/webauthn/login/finish
أرسل raw assertion Response من navigator.credentials.get() مع البريد الإلكتروني:
{
"email": "[email protected]"
}
أضف credential assertion كـ request body كامل (الـ SDK يتولى الدمج تلقائيًا).
الـ Response — 200 OK
{
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
"expiresIn": 3600,
"message": "WebAuthn login successful"
}
إدارة الـ Credentials
عرض الـ Credentials
GET /api/v1/mfa/webauthn/credentials
Header مطلوب: Authorization: Bearer <accessToken>
الـ Response — 200 OK
{
"credentials": [
{
"id": "cred_01J8X...",
"name": "My MacBook Pro",
"transports": ["internal"],
"createdAt": "2026-02-20T08:30:00Z",
"lastUsedAt": "2026-02-24T10:00:00Z",
"aaguidReadable": "YWFndWlkX2Jhc2U2NA=="
}
],
"total": 1
}
| الحقل | الوصف |
|---|---|
id | UUID لسجل الـ Credential |
name | التسمية المقروءة المُعيّنة أثناء التسجيل |
transports | طريقة اتصال الـ Authenticator: internal, usb, nfc, ble, hybrid |
createdAt | تاريخ تسجيل الـ Credential |
lastUsedAt | آخر مصادقة ناجحة |
aaguidReadable | AAGUID بترميز Base64 (يحدد طراز الـ Authenticator) |
حذف Credential
DELETE /api/v1/mfa/webauthn/credentials/{id}
Header مطلوب: Authorization: Bearer <accessToken>
الـ Response — 200 OK
{
"message": "WebAuthn credential deleted successfully"
}
إذا حذف المستخدم آخر WebAuthn credential لديه وليس لديه كلمة مرور أو أسلوب MFA ثانٍ، فقد يقفل نفسه خارج الحساب. يجب أن تُحذّر واجهتك المستخدم قبل حذف آخر credential.
أمثلة الكود
مسار التسجيل الكامل (JavaScript)
import { IthbatSDK } from '@ithbatiam/sdk';
const ithbat = new IthbatSDK({ tenantId: 'ten_01J8X...' });
async function registerPasskey(credentialName) {
// Step 1: Get registration options from Ithbat IAM
const { options } = await ithbat.webauthn.beginRegistration({
credentialName,
});
// Step 2: Call the WebAuthn API — browser prompts for biometric/key
const credential = await navigator.credentials.create(options);
// Step 3: Send the result to Ithbat IAM
const result = await ithbat.webauthn.finishRegistration(credential);
console.log('Passkey registered:', result.credentialId);
}
مسار المصادقة الكامل (JavaScript)
async function loginWithPasskey(email) {
// Step 1: Get authentication options
const { options } = await ithbat.webauthn.beginLogin({ email });
// Step 2: Call the WebAuthn API — browser prompts for biometric/key
const assertion = await navigator.credentials.get(options);
// Step 3: Verify with Ithbat IAM
const session = await ithbat.webauthn.finishLogin({ email, assertion });
console.log('Signed in:', session.accessToken);
}
إدارة الـ Credentials
// List all registered passkeys
const { credentials } = await ithbat.webauthn.listCredentials();
for (const cred of credentials) {
console.log(`${cred.name} — last used ${cred.lastUsedAt}`);
}
// Delete a credential by ID
await ithbat.webauthn.deleteCredential('cred_01J8X...');
استخدام Raw WebAuthn API (بدون SDK)
// Begin registration
const startResp = await fetch('/api/v1/mfa/webauthn/register/start', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ credentialName: 'My Device' }),
});
const { options } = await startResp.json();
// Decode base64url challenge and user.id
options.publicKey.challenge = base64urlDecode(options.publicKey.challenge);
options.publicKey.user.id = base64urlDecode(options.publicKey.user.id);
// Create credential
const credential = await navigator.credentials.create(options);
// Encode response for transmission
const credentialForTransmit = {
id: credential.id,
rawId: base64urlEncode(credential.rawId),
type: credential.type,
response: {
attestationObject: base64urlEncode(credential.response.attestationObject),
clientDataJSON: base64urlEncode(credential.response.clientDataJSON),
},
};
// Finish registration
await fetch('/api/v1/mfa/webauthn/register/finish', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(credentialForTransmit),
});
الـ Ithbat SDK يتولى كل عمليات base64url encoding/decoding تلقائيًا. استخدم الـ SDK إلا إذا كان لديك سبب محدد لاستدعاء الـ Raw API.
معالجة الأخطاء
| HTTP Status | Error Code | السبب |
|---|---|---|
400 | VALIDATION_ERROR | حقل email ناقص في login start/finish |
401 | UNAUTHORIZED | المصادقة مطلوبة للتسجيل |
401 | UNAUTHORIZED | Assertion verification فشل (مفتاح خطأ أو origin خطأ) |
401 | UNAUTHORIZED | ما لقينا مستخدم لهذا البريد |
404 | NOT_FOUND | ما في WebAuthn credentials مسجلة للمستخدم |
الخطوات التالية
- MFA — استخدام WebAuthn كعامل ثانٍ إلى جانب كلمة المرور
- Token Lifecycle — إدارة الـ Tokens الصادرة بعد WebAuthn login
- Passwordless — Magic Link كأسلوب Passwordless بديل