انتقل إلى المحتوى الرئيسي

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 authenticatorFace ID, Touch ID, Windows Hello, Android biometricsمدمج في الجهاز — الـ Credential لا يغادر الجهاز أبدًا
Roaming authenticatorYubiKey 5, Google Titanمفتاح أمان عبر USB أو NFC أو Bluetooth
Passkeys (synced)iCloud Keychain, Google Password ManagerPlatform credential يُزامن عبر الأجهزة بالسحابة
ملاحظة

WebAuthn credentials المسجلة في Ithbat IAM تشتغل كعامل مصادقة أساسي مستقل (بدون كلمة مرور) أو كعامل ثانٍ قوي بعد تسجيل الدخول بكلمة المرور.


توافق المتصفحات

WebAuthn مدعوم في كل المتصفحات الحديثة:

المتصفحالحد الأدنى
Chrome67+
Safari14+
Firefox60+
Edge18+
Samsung Internet10.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
}
الحقلالوصف
idUUID لسجل الـ Credential
nameالتسمية المقروءة المُعيّنة أثناء التسجيل
transportsطريقة اتصال الـ Authenticator: internal, usb, nfc, ble, hybrid
createdAtتاريخ تسجيل الـ Credential
lastUsedAtآخر مصادقة ناجحة
aaguidReadableAAGUID بترميز Base64 (يحدد طراز الـ Authenticator)

حذف Credential

DELETE /api/v1/mfa/webauthn/credentials/&#123;id&#125;

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 StatusError Codeالسبب
400VALIDATION_ERRORحقل email ناقص في login start/finish
401UNAUTHORIZEDالمصادقة مطلوبة للتسجيل
401UNAUTHORIZEDAssertion verification فشل (مفتاح خطأ أو origin خطأ)
401UNAUTHORIZEDما لقينا مستخدم لهذا البريد
404NOT_FOUNDما في WebAuthn credentials مسجلة للمستخدم

الخطوات التالية

  • MFA — استخدام WebAuthn كعامل ثانٍ إلى جانب كلمة المرور
  • Token Lifecycle — إدارة الـ Tokens الصادرة بعد WebAuthn login
  • Passwordless — Magic Link كأسلوب Passwordless بديل