أول واجهة برمجة تطبيقات لـ Android FIDO2

1. مقدمة

ما هي واجهة برمجة تطبيقات FIDO2؟

تسمح واجهة برمجة تطبيقات FIDO2 لتطبيقات Android بإنشاء بيانات اعتماد قوية مستندة إلى مفتاح عام مُصدَّق عليها واستخدامها بغرض مصادقة المستخدمين. توفّر واجهة برمجة التطبيقات تنفيذ WebAuthn Client، الذي يتيح استخدام أدوات المصادقة القابلة للفهرسة (BLE) وNFC وUSB (مفاتيح الأمان) للتجوال، بالإضافة إلى برنامج مصادقة على النظام الأساسي يتيح للمستخدم إجراء المصادقة باستخدام بصمة الإصبع أو قفل الشاشة.

ما الذي ستنشئه...

في هذا الدرس التطبيقي حول الترميز، ستنشئ تطبيق Android مع وظيفة إعادة مصادقة بسيطة باستخدام أداة استشعار بصمة الإصبع. "إعادة المصادقة" هو عندما يسجِّل مستخدم الدخول إلى تطبيق، ثم يعيد المصادقة عند عودته إلى تطبيقك، أو عند محاولة الوصول إلى قسم مهم في تطبيقك. ويُشار إلى الحالة الثانية أيضًا باسم "المصادقة المرحة".

المعلومات التي ستطّلع عليها

ستتعلم كيفية طلب واجهة برمجة تطبيقات Android FIDO2 والخيارات التي يمكنك تقديمها لتلبية احتياجات مختلفة. ستتعرف أيضًا على أفضل الممارسات المتعلقة بإعادة المصادقة.

ما ستحتاجه...

  • جهاز Android مزوّد بمستشعر بصمة الإصبع (يمكن أن يوفّر قفل الشاشة وظيفة مكافئة للتحقّق من هوية المستخدم، حتى في حال عدم توفُّر أداة استشعار بصمة الإصبع)
  • الإصدار 7.0 من نظام التشغيل Android أو إصدار أحدث مع آخر التحديثات احرص على تسجيل بصمة الإصبع (أو قفل الشاشة).

2. بدء الإعداد

استنساخ المستودع

راجع مستودع جيت هب.

https://github.com/android/codelab-fido2

$ git clone https://github.com/android/codelab-fido2.git

ما الذي سننفّذه؟

  • السماح للمستخدمين بتسجيل "أداة المصادقة على النظام الأساسي للتحقق من هوية المستخدم" (سيعمل هاتف Android الذي يحتوي على أداة استشعار بصمة الإصبع كجهاز واحد).
  • يتم السماح للمستخدمين بإعادة المصادقة على أنفسهم للوصول إلى التطبيق باستخدام بصمة إصبعهم.

يمكنك معاينة ما تريد إنشاءه هنا.

بدء مشروع الدرس التطبيقي حول الترميز

ويرسل التطبيق المكتمل الطلبات إلى خادم على الرابط https://webauthn-codelab.glitch.me. يمكنك تجربة إصدار الويب من التطبيق نفسه هناك.

c2234c42ba8a6ef1.png

ستعمل على الإصدار الخاص بك من التطبيق.

  1. انتقِل إلى صفحة تعديل الموقع الإلكتروني على https://glitch.com/edit/#!/webauthn-codelab.
  2. البحث عن الخيار "إنشاء ريمكس للتعديل" في أعلى الجانب الأيسر. من خلال الضغط على الزر، يمكنك "شوكة" الرمز ومواصلة نسختك مع عنوان URL جديد للمشروع. 9ef108869885e4ce.png
  3. انسخ اسم المشروع في أعلى يمين الصفحة (يمكنك تعديله كما تريد). c91d0d59c61021a4.png
  4. الصقه في القسم HOSTNAME للملف .env في هذا الخطأ. 889b55b1cf74b894.png

3- ربط تطبيقك وموقع إلكتروني بروابط مواد العرض الرقمية

لاستخدام واجهة برمجة التطبيقات FIDO2 API على تطبيق Android، عليك ربطها بموقع إلكتروني ومشاركة بيانات الاعتماد بينها. ولإجراء ذلك، يمكنك الاستفادة من روابط مواد العرض الرقمية. يمكنك الإفصاح عن عمليات الربط من خلال استضافة ملف روابط التنقل إلى مواد العرض الرقمية بتنسيق JSON على موقعك الإلكتروني وإضافة رابط يؤدي إلى ملف "رابط مواد العرض الرقمية" إلى ملف بيان تطبيقك.

استضافة .well-known/assetlinks.json في نطاقك

يمكنك تحديد عملية ربط بين تطبيقك والموقع الإلكتروني من خلال إنشاء ملف JSON ووضعه في .well-known/assetlinks.json. لحسن الحظ، لدينا رمز خادم يعرض ملف assetlinks.json تلقائيًا، وذلك من خلال إضافة مَعلمات البيئة التالية إلى ملف .env بشكل خلل:

  • ANDROID_PACKAGENAME: اسم حزمة تطبيقك (com.example.android.fido2)
  • ANDROID_SHA256HASH: تجزئة SHA256 لشهادة التوقيع

للحصول على تجزئة SHA256 لشهادة توقيع المطوّر، استخدِم الأمر أدناه. كلمة المرور التلقائية لملف تخزين المفاتيح لتصحيح الأخطاء هي "android".

$ keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore

من خلال الوصول إلى https://<your-project-name>.glitch.me/.well-known/assetlinks.json، من المفترض أن تظهر لك سلسلة JSON مثل هذه:

[{
  "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
  "target": {
    "namespace": "web",
    "site": "https://<your-project-name>.glitch.me"
  }
}, {
  "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.android.fido2",
    "sha256_cert_fingerprints": ["DE:AD:BE:EF:..."]
  }
}]

فتح المشروع في "استوديو Android"

النقر على "فتح مشروع حالي على استوديو Android" على شاشة الترحيب في "استوديو Android"

اختر "android" مجلّد داخل المستودع.

1062875cf11ffb95.png

ربط التطبيق بالريمكس

افتح ملف gradle.properties. في أسفل الملف، غيِّر عنوان URL المضيف إلى ريمكس Glitch الذي أنشأته للتو.

// ...

# The URL of the server
host=https://<your-project-name>.glitch.me

في هذه المرحلة، يجب أن تكون إعدادات ميزة "روابط مواد العرض الرقمية" جاهزة للاستخدام.

4. التعرّف على طريقة عمل التطبيق الآن

لنبدأ بالتحقق من كيفية عمل التطبيق الآن. تأكَّد من اختيار app-start في مربّع التحرير والسرد "تشغيل الإعداد". انقر فوق "Run" (تشغيل) (مثلّث أخضر بجانب مربع التحرير والسرد) لتشغيل التطبيق على جهاز Android المرتبط.

29351fb97062b43c.png

عند تشغيل التطبيق، ستظهر لك شاشة لكتابة اسم المستخدم. دَهْ UsernameFragment. لأغراض التوضيح، يقبل التطبيق والخادم أي اسم مستخدم. ما عليك سوى كتابة شيء والضغط على "التالي".

bd9007614a9a3644.png

الشاشة التالية التي تراها هي AuthFragment. ويمكن للمستخدم تسجيل الدخول من هنا باستخدام كلمة مرور. سنضيف لاحقًا ميزة لتسجيل الدخول باستخدام FIDO2 هنا. ومرة أخرى، وبغرض التوضيح، يقبل التطبيق والخادم أي كلمة مرور. ما عليك سوى كتابة شيء والضغط على "تسجيل الدخول".

d9caba817a0a99bd.png

هذه هي الشاشة الأخيرة في هذا التطبيق، "HomeFragment". لا تظهر حاليًا سوى قائمة فارغة من بيانات الاعتماد هنا. الضغط على "إعادة المصادقة" يعيدك إلى AuthFragment. الضغط على "تسجيل الخروج" يعيدك إلى UsernameFragment. زر الإجراء الرئيسي مع علامة "+" لا تفعل أي شيء الآن، ولكنها ستبدأ تسجيل

بيانات اعتماد جديدة بعد تنفيذ مسار تسجيل FIDO2.

1cfcc6c884020e37.png

قبل البدء في الترميز، إليك أسلوب مفيد. في "استوديو Android"، اضغط على "قائمة المهام" في أسفل الصفحة سيعرض هذا القسم قائمة بجميع المهام في هذا الدرس التطبيقي حول الترميز. سنبدأ بأول TODO في القسم التالي.

e5a811bbc7cd7b30.png

5- تسجيل بيانات اعتماد باستخدام بصمة إصبع

لتفعيل المصادقة باستخدام بصمة الإصبع، يجب أولاً تسجيل بيانات الاعتماد التي تم إنشاؤها من قِبل مستخدم يُثبت صحة برنامج المصادقة على النظام الأساسي، وهو أداة مصادقة مضمّنة في الجهاز تتحقّق من المستخدم باستخدام المقاييس الحيوية، مثل أداة استشعار بصمة الإصبع.

37ce78fdf2759832.png

كما رأينا في القسم السابق، لا يفعل زر الإجراء الرئيسي أي شيء الآن. لنرَ كيف يمكننا تسجيل بيانات اعتماد جديدة.

طلب واجهة برمجة تطبيقات الخادم: /auth/registerRequest

افتح AuthRepository.kt وابحث عن TODO(1).

هنا، registerRequest هي الطريقة التي يتم استدعاؤها عند الضغط على زر الإجراء الرئيسي (FAB). ونود أن نجعل هذه الطريقة تستدعي واجهة برمجة تطبيقات الخادم /auth/registerRequest. تعرض واجهة برمجة التطبيقات رمز ApiResult يتضمّن كل PublicKeyCredentialCreationOptions الذي يحتاجه العميل لإنشاء بيانات اعتماد جديدة.

يمكننا بعد ذلك الاتصال بـ getRegisterPendingIntent مع توفير الخيارات. تعرض واجهة برمجة تطبيقات FIDO2 هذه رمز PendingIntent لنظام التشغيل Android لفتح مربع حوار بصمة الإصبع وإنشاء بيانات اعتماد جديدة، ويمكننا إعادة رمز PendingIntent هذا إلى المتصل.

وعندئذٍ ستبدو الطريقة كما هو موضح أدناه.

suspend fun registerRequest(): PendingIntent? {
  fido2ApiClient?.let { client ->
    try {
      val sessionId = dataStore.read(SESSION_ID)!!
      when (val apiResult = api.registerRequest(sessionId)) {
        ApiResult.SignedOutFromServer -> forceSignOut()
        is ApiResult.Success -> {
          if (apiResult.sessionId != null) {
            dataStore.edit { prefs ->
              prefs[SESSION_ID] = apiResult.sessionId
            }
          }
          val task = client.getRegisterPendingIntent(apiResult.data)
          return task.await()
        }
      }
    } catch (e: Exception) {
      Log.e(TAG, "Cannot call registerRequest", e)
    }
  }
  return null
}

فتح مربع حوار بصمة الإصبع للتسجيل

افتح HomeFragment.kt وابحث عن TODO(2).

هذا هو المكان الذي تسترجع فيه واجهة المستخدم Intent من AuthRepository. في هذه الحالة، سنستخدم عضو createCredentialIntentLauncher لتفعيل رمز PendingIntent الذي حصلنا عليه كنتيجة للخطوة السابقة. سيؤدي هذا إلى فتح مربع حوار لإنشاء بيانات الاعتماد.

binding.add.setOnClickListener {
  lifecycleScope.launch {
    val intent = viewModel.registerRequest()
    if (intent != null) {
      createCredentialIntentLauncher.launch(
        IntentSenderRequest.Builder(intent).build()
      )
    }
  }
}

تلقّي ActivityResult مع بيانات الاعتماد الجديدة

افتح HomeFragment.kt وابحث عن TODO(3).

ويتم استدعاء طريقة handleCreateCredentialResult هذه بعد إغلاق مربّع حوار بصمة الإصبع. إذا تم إنشاء بيانات اعتماد بنجاح، سيحتوي عضو data في ActivityResult على معلومات بيانات الاعتماد.

أولاً، علينا استخراج PublicKeyCredential من data. يحتوي Intent للبيانات على حقل إضافي لمصفوفة بايت مع المفتاح Fido.FIDO2_KEY_CREDENTIAL_EXTRA. يمكنك استخدام طريقة ثابتة في PublicKeyCredential تُسمّى deserializeFromBytes لتحويل مصفوفة البايت إلى كائن PublicKeyCredential.

تحقَّق بعد ذلك مما إذا كان عضو response لكائن بيانات الاعتماد هذا AuthenticationErrorResponse. إذا كانت كذلك، هذا يعني أنّه حدث خطأ أثناء إنشاء بيانات الاعتماد. وإلا فيمكننا إرسال بيانات الاعتماد إلى الواجهة الخلفية لدينا.

ستبدو الطريقة النهائية على النحو التالي:

private fun handleCreateCredentialResult(activityResult: ActivityResult) {
  val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
  when {
    activityResult.resultCode != Activity.RESULT_OK ->
      Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_LONG).show()
    bytes == null ->
      Toast.makeText(requireContext(), R.string.credential_error, Toast.LENGTH_LONG)
        .show()
    else -> {
      val credential = PublicKeyCredential.deserializeFromBytes(bytes)
      val response = credential.response
      if (response is AuthenticatorErrorResponse) {
        Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_LONG)
          .show()
      } else {
        viewModel.registerResponse(credential)
      }
    }
  }
}

طلب واجهة برمجة تطبيقات الخادم: /auth/registerResponse

افتح AuthRepository.kt وابحث عن TODO(4).

يتم استدعاء طريقة registerResponse هذه بعد أن تنشئ واجهة المستخدم بيانات اعتماد جديدة بنجاح، ونريد إعادة إرسالها إلى الخادم.

يحتوي الكائن PublicKeyCredential على معلومات عن بيانات الاعتماد التي تم إنشاؤها حديثًا بداخله. نريد الآن أن نتذكر مفتاحنا المحلي كي يتسنى لنا تمييزه عن المفاتيح الأخرى المسجلة على الخادم. في الكائن PublicKeyCredential، خذ السمة rawId الخاصة به ووضعها في متغيّر سلسلة محلية باستخدام toBase64.

نحن الآن جاهزون لإرسال المعلومات إلى الخادم. استخدِم api.registerResponse لطلب واجهة برمجة تطبيقات الخادم وإرسال الردّ. تحتوي القيمة المعروضة على قائمة بجميع بيانات الاعتماد المسجّلة على الخادم، بما في ذلك بيانات الاعتماد الجديدة.

وأخيرًا، يمكننا حفظ النتائج في DataStore. يجب حفظ قائمة بيانات الاعتماد بالمفتاح CREDENTIALS كملف StringSet. يمكنك استخدام toStringSet لتحويل قائمة بيانات الاعتماد إلى StringSet.

بالإضافة إلى ذلك، نحفظ رقم تعريف بيانات الاعتماد مع المفتاح LOCAL_CREDENTIAL_ID.

suspend fun registerResponse(credential: PublicKeyCredential) {
  try {
    val sessionId = dataStore.read(SESSION_ID)!!
    val credentialId = credential.rawId.toBase64()
    when (val result = api.registerResponse(sessionId, credential)) {
      ApiResult.SignedOutFromServer -> forceSignOut()
      is ApiResult.Success -> {
        dataStore.edit { prefs ->
          result.sessionId?.let { prefs[SESSION_ID] = it }
          prefs[CREDENTIALS] = result.data.toStringSet()
          prefs[LOCAL_CREDENTIAL_ID] = credentialId
        }
      }
    }
  } catch (e: ApiException) {
    Log.e(TAG, "Cannot call registerResponse", e)
  }
}

قم بتشغيل التطبيق، وستتمكن من النقر فوق FAB وتسجيل بيانات اعتماد جديدة.

7d64d9289c5a3cbc.png

6- مصادقة المستخدم باستخدام بصمة الإصبع

لدينا الآن بيانات اعتماد مسجَّلة في التطبيق والخادم. يمكننا الآن استخدامه للسماح للمستخدم بتسجيل الدخول. نحن بصدد إضافة ميزة تسجيل الدخول ببصمة الإصبع إلى "AuthFragment". عندما يصل المستخدم إليها، يظهر مربّع حوار بصمة الإصبع. عندما تنجح المصادقة، تتم إعادة توجيه المستخدم إلى HomeFragment.

طلب واجهة برمجة تطبيقات الخادم: /auth/signinRequest

افتح AuthRepository.kt وابحث عن TODO(5).

يتم استدعاء طريقة signinRequest هذه عند فتح AuthFragment. هنا، نريد طلب الخادم ومعرفة ما إذا كان يمكننا السماح للمستخدم بتسجيل الدخول باستخدام FIDO2.

أولاً، علينا استرداد PublicKeyCredentialRequestOptions من الخادم. استخدِم api.signInRequest لطلب واجهة برمجة تطبيقات الخادم. تحتوي قيمة ApiResult المعروضة على PublicKeyCredentialRequestOptions.

من خلال PublicKeyCredentialRequestOptions، يمكننا استخدام FIDO2 API getSignIntent لإنشاء رمز PendingIntent لفتح مربّع حوار الملف المرجعي.

وأخيرًا، يمكننا إرجاع كائن PendingIntent إلى واجهة المستخدم.

suspend fun signinRequest(): PendingIntent? {
  fido2ApiClient?.let { client ->
    val sessionId = dataStore.read(SESSION_ID)!!
    val credentialId = dataStore.read(LOCAL_CREDENTIAL_ID)
    if (credentialId != null) {
      when (val apiResult = api.signinRequest(sessionId, credentialId)) {
        ApiResult.SignedOutFromServer -> forceSignOut()
        is ApiResult.Success -> {
          val task = client.getSignPendingIntent(apiResult.data)
          return task.await()
        }
      }
    }
  }
  return null
}

فتح مربع حوار بصمات الإصبع للتأكيد

افتح AuthFragment.kt وابحث عن TODO(6).

وهذا الأمر يشبه إلى حد كبير ما فعلناه في عملية التسجيل. يمكننا فتح مربّع حوار بصمة الإصبع مع عضو signIntentLauncher.

viewLifecycleOwner.lifecycleScope.launchWhenStarted {
  launch {
    viewModel.signinRequests.collect { intent ->
      signIntentLauncher.launch(
        IntentSenderRequest.Builder(intent).build()
      )
    }
  }
  launch {
    ...
  }
}

التعامل مع ActivityResult

افتح AuthFragment.kt وابحث عن TODO(7).

مرة أخرى، هذه هي الطريقة نفسها التي اتّبعناها في عملية التسجيل. يمكننا استخراج PublicKeyCredential والتحقق من وجود خطأ وتمريره إلى ViewModel.

private fun handleSignResult(activityResult: ActivityResult) {
  val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
  when {
    activityResult.resultCode != Activity.RESULT_OK ->
      Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_SHORT).show()
    bytes == null ->
      Toast.makeText(requireContext(), R.string.auth_error, Toast.LENGTH_SHORT)
        .show()
    else -> {
      val credential = PublicKeyCredential.deserializeFromBytes(bytes)
      val response = credential.response
      if (response is AuthenticatorErrorResponse) {
        Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_SHORT)
          .show()
      } else {
        viewModel.signinResponse(credential)
      }
    }
  }
}

طلب واجهة برمجة تطبيقات الخادم: /auth/signinResponse

افتح AuthRepository.kt وابحث عن TODO(8).

يحتوي الكائن PublicKeyCredential على رقم تعريف بيانات اعتماد بالتنسيق keyHandle. تمامًا كما فعلنا في تدفق التسجيل، دعنا نحفظ هذا في متغير سلسلة محلية حتى نتمكن من تخزينه لاحقًا.

نحن الآن جاهزون لطلب واجهة برمجة تطبيقات الخادم باستخدام api.signinResponse. تحتوي القيمة المعروضة على قائمة ببيانات الاعتماد.

في هذه المرحلة، تم تسجيل الدخول بنجاح. علينا تخزين جميع النتائج في DataStore. يجب تخزين قائمة بيانات الاعتماد على أنّها StringSet باستخدام المفتاح CREDENTIALS. يجب أن يتم تخزين رقم تعريف بيانات الاعتماد المحلي الذي حفظناه أعلاه كسلسلة تحتوي على المفتاح LOCAL_CREDENTIAL_ID.

وأخيرًا، نحتاج إلى تعديل حالة تسجيل الدخول لتتمكّن واجهة المستخدم من إعادة توجيه المستخدم إلى HomeFragment. ويمكن إجراء ذلك من خلال إرسال كائن SignInState.SignedIn إلى SharedFlow المُسمّى signInStateMutable. نريد أيضًا استدعاء refreshCredentials لجلب بيانات اعتماد المستخدم حتى يتم إدراجها في واجهة المستخدم.

suspend fun signinResponse(credential: PublicKeyCredential) {
  try {
    val username = dataStore.read(USERNAME)!!
    val sessionId = dataStore.read(SESSION_ID)!!
    val credentialId = credential.rawId.toBase64()
    when (val result = api.signinResponse(sessionId, credential)) {
      ApiResult.SignedOutFromServer -> forceSignOut()
      is ApiResult.Success -> {
        dataStore.edit { prefs ->
          result.sessionId?.let { prefs[SESSION_ID] = it }
          prefs[CREDENTIALS] = result.data.toStringSet()
          prefs[LOCAL_CREDENTIAL_ID] = credentialId
        }
        signInStateMutable.emit(SignInState.SignedIn(username))
        refreshCredentials()
      }
    }
  } catch (e: ApiException) {
    Log.e(TAG, "Cannot call registerResponse", e)
  }
}

شغِّل التطبيق وانقر على "إعادة المصادقة" لفتح AuthFragment. من المفترض أن يظهر لك الآن مربع حوار بصمة إصبع يطلب منك تسجيل الدخول باستخدام بصمة إصبعك.

45f81419f84952c8.png

تهانينا! لقد تعرّفت الآن على كيفية استخدام FIDO2 API على Android للتسجيل وتسجيل الدخول.

7. تهانينا!

لقد أكملت الدرس التطبيقي حول الترميز بنجاح، وهو أول واجهة برمجة تطبيقات Android FIDO2 API.

ما تعلمته

  • كيفية تسجيل بيانات الاعتماد باستخدام مستخدم يتحقّق من مصادقة النظام الأساسي
  • كيفية مصادقة مستخدم باستخدام برنامج مصادقة مسجَّل
  • الخيارات المتاحة لتسجيل برنامج مصادقة جديد.
  • أفضل ممارسات تجربة المستخدم لإعادة المصادقة باستخدام أداة استشعار المقاييس الحيوية.

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

  • تعرّف على كيفية إنشاء تجربة مماثلة في موقع ويب.

يمكنك التعرّف على ذلك من خلال تجربة الدرس التطبيقي حول الترميز WebAuthn الخاص بك.

الموارد

نشكر يوري أكيرمان من تحالف FIDO Alliance على مساعدتك.