اولین API اندروید FIDO2 شما

1. مقدمه

FIDO2 API چیست؟

FIDO2 API به برنامه‌های Android اجازه می‌دهد تا اعتبارنامه‌های قوی مبتنی بر کلید عمومی تأیید شده را برای احراز هویت کاربران ایجاد و استفاده کنند. API یک پیاده‌سازی WebAuthn Client را ارائه می‌کند که از استفاده از احراز هویت رومینگ BLE، NFC و USB (کلیدهای امنیتی) و همچنین یک تأییدکننده پلتفرم پشتیبانی می‌کند که به کاربر اجازه می‌دهد با استفاده از اثر انگشت یا قفل صفحه خود احراز هویت کند.

چیزی که خواهی ساخت...

در این کد لبه، شما قصد دارید یک اپلیکیشن اندرویدی با قابلیت احراز هویت مجدد ساده با استفاده از حسگر اثر انگشت بسازید. «تأیید هویت مجدد» زمانی است که کاربر به برنامه‌ای وارد می‌شود، سپس وقتی به برنامه شما برمی‌گردد یا زمانی که سعی می‌کند به بخش مهمی از برنامه شما دسترسی پیدا کند ، دوباره احراز هویت می‌شود . مورد دوم نیز به عنوان "تأیید هویت مرحله به مرحله" نامیده می شود.

آنچه یاد خواهید گرفت...

نحوه فراخوانی Android FIDO2 API و گزینه هایی را که می توانید برای مناسبت های مختلف ارائه دهید، یاد خواهید گرفت. همچنین بهترین شیوه های خاص را مجدداً تأیید خواهید کرد.

آنچه شما نیاز خواهید داشت ...

  • دستگاه اندرویدی با حسگر اثر انگشت (حتی بدون حسگر اثر انگشت، قفل صفحه می‌تواند عملکردی معادل برای تأیید کاربر ارائه دهد)
  • سیستم عامل اندروید 7.0 یا بالاتر با آخرین به روز رسانی ها. حتما اثر انگشت (یا قفل صفحه) را ثبت کنید.

2. راه اندازی

Repository را شبیه سازی کنید

مخزن GitHub را بررسی کنید.

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

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

قرار است چه چیزی را اجرا کنیم؟

  • به کاربران اجازه دهید «تأیید کننده اعتبار پلتفرم تأییدکننده کاربر» را ثبت کنند (تلفن اندرویدی با حسگر اثر انگشت خود به عنوان یکی عمل می کند).
  • به کاربران اجازه دهید با استفاده از اثر انگشت خود را مجدداً در برنامه تأیید کنند.

از اینجا می توانید پیش نمایش آنچه را که می خواهید بسازید مشاهده کنید.

پروژه Codelab خود را شروع کنید

برنامه تکمیل شده درخواست ها را به سروری در 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 در یک برنامه اندروید، آن را با یک وب سایت مرتبط کنید و اعتبارنامه ها را بین آنها به اشتراک بگذارید. برای انجام این کار، از پیوندهای دارایی دیجیتال استفاده کنید. می‌توانید با میزبانی فایل JSON پیوندهای دارایی دیجیتال در وب‌سایت خود، و افزودن پیوندی به فایل پیوند دارایی دیجیتال به مانیفست برنامه خود، ارتباط خود را اعلام کنید.

.well-known/assetlinks.json در دامنه خود میزبانی کنید

می توانید با ایجاد یک فایل JSON و قرار دادن آن در .well-known/assetlinks.json ، ارتباطی بین برنامه خود و وب سایت تعریف کنید. خوشبختانه، ما یک کد سرور داریم که فایل assetlinks.json را به‌طور خودکار نمایش می‌دهد، فقط با افزودن پارامترهای محیط زیر به فایل .env در نقص:

  • ANDROID_PACKAGENAME : نام بسته برنامه شما (com.example.android.fido2)
  • ANDROID_SHA256HASH : SHA256 گواهی امضای شما را هش کنید

برای دریافت هش SHA256 گواهی امضای توسعه دهنده خود، از دستور زیر استفاده کنید. رمز عبور پیش‌فرض فروشگاه کلید دیباگ «اندروید» است.

$ 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 Studio» کلیک کنید.

پوشه "اندروید" را در داخل مخزن بررسی کنید.

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 است. در حال حاضر، فقط یک لیست خالی از اعتبارنامه ها را در اینجا می بینید. با فشار دادن "Reauth" به AuthFragment برمی گردید. با فشار دادن "Sign Out" به UsernameFragment برمی گردید. دکمه اکشن شناور با علامت "+" اکنون کاری انجام نمی دهد، اما ثبت a را آغاز می کند

پس از اجرای جریان ثبت نام FIDO2، اعتبار جدید

1cfcc6c884020e37.png

قبل از شروع کدنویسی، در اینجا یک تکنیک مفید وجود دارد. در Android Studio، "TODO" را در پایین فشار دهید. لیستی از همه TODOها در این Codelab را نشان می دهد. در بخش بعدی با اولین TODO شروع می کنیم.

e5a811bbc7cd7b30.png

5. با استفاده از اثر انگشت، اعتبارنامه را ثبت کنید

برای فعال کردن احراز هویت با استفاده از اثر انگشت، ابتدا باید یک اعتبار ایجاد شده توسط یک تأییدکننده پلتفرم تأییدکننده کاربر - یک تأییدکننده تعبیه‌شده در دستگاه که کاربر را با استفاده از بیومتریک، مانند حسگر اثر انگشت تأیید می‌کند، ثبت کنید.

37ce78fdf2759832.png

همانطور که در بخش قبل دیدیم، دکمه اکشن شناور اکنون کاری انجام نمی دهد. بیایید ببینیم چگونه می توانیم یک اعتبار جدید ثبت کنیم.

با API سرور تماس بگیرید: /auth/registerRequest

AuthRepository.kt را باز کنید و TODO(1) را پیدا کنید.

در اینجا registerRequest متدی است که با فشار دادن FAB فراخوانی می شود. می‌خواهیم این روش API سرور را فراخوانی کند /auth/registerRequest . API یک ApiResult با تمام PublicKeyCredentialCreationOptions که کلاینت برای ایجاد یک اعتبار جدید به آن نیاز دارد، برمی گرداند.

سپس می توانیم getRegisterPendingIntent با گزینه ها فراخوانی کنیم. این FIDO2 API یک Android PendingIntent را برای باز کردن یک گفتگوی اثر انگشت و ایجاد یک اعتبار جدید برمی‌گرداند و ما می‌توانیم آن 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) را پیدا کنید.

اینجاست که UI 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)
      }
    }
  }
}

با API سرور تماس بگیرید: /auth/registerResponse

AuthRepository.kt را باز کنید و TODO(4) را پیدا کنید.

این متد registerResponse پس از اینکه UI با موفقیت یک اعتبار جدید را ایجاد کرد فراخوانی می شود و ما می خواهیم آن را به سرور ارسال کنیم.

شی PublicKeyCredential اطلاعاتی در مورد اعتبار جدید ایجاد شده در داخل دارد. اکنون می خواهیم شناسه کلید محلی خود را به خاطر بسپاریم تا بتوانیم آن را از سایر کلیدهای ثبت شده در سرور متمایز کنیم. در شی PublicKeyCredential ، ویژگی rawId آن را بگیرید و با استفاده از toBase64 در یک متغیر رشته محلی قرار دهید.

اکنون آماده ارسال اطلاعات به سرور هستیم. از api.registerResponse برای فراخوانی API سرور و ارسال پاسخ استفاده کنید. مقدار برگشتی شامل لیستی از تمام اعتبارنامه های ثبت شده در سرور، از جمله اعتبار جدید است.

در نهایت، می‌توانیم نتایج را در 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 هدایت می شود.

با API سرور تماس بگیرید: /auth/signinRequest

AuthRepository.kt را باز کنید و TODO(5) را پیدا کنید.

این متد signinRequest زمانی فراخوانی می شود که AuthFragment باز شود. در اینجا، می‌خواهیم سرور را درخواست کنیم و ببینیم که آیا می‌توانیم به کاربر اجازه دهیم با FIDO2 وارد شود.

ابتدا باید PublicKeyCredentialRequestOptions از سرور بازیابی کنیم. برای فراخوانی API سرور از api.signInRequest استفاده کنید. ApiResult برگشتی حاوی PublicKeyCredentialRequestOptions است.

با PublicKeyCredentialRequestOptions ، می‌توانیم از FIDO2 API getSignIntent برای ایجاد یک PendingIntent برای باز کردن گفتگوی اثر انگشت استفاده کنیم.

در نهایت، می‌توانیم PendingIntent را به UI برگردانیم.

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)
      }
    }
  }
}

API سرور را فراخوانی کنید: /auth/signinResponse

AuthRepository.kt را باز کنید و TODO(8) را پیدا کنید.

شی PublicKeyCredential یک شناسه اعتبار در آن به عنوان keyHandle دارد. درست همانطور که در جریان ثبت نام انجام دادیم، اجازه دهید این را در یک متغیر رشته محلی ذخیره کنیم تا بتوانیم بعداً آن را ذخیره کنیم.

اکنون آماده هستیم تا API سرور را با api.signinResponse فراخوانی کنیم. مقدار بازگشتی حاوی لیستی از اعتبارنامه ها است.

در این مرحله، ورود به سیستم با موفقیت انجام می شود. ما باید تمام نتایج را در DataStore خود ذخیره کنیم. لیست اعتبارنامه ها باید به عنوان StringSet با کلید CREDENTIALS ذخیره شود. شناسه اعتبار محلی که در بالا ذخیره کردیم باید به عنوان یک رشته با کلید LOCAL_CREDENTIAL_ID ذخیره شود.

در نهایت، باید وضعیت ورود به سیستم را به‌روزرسانی کنیم تا رابط کاربری بتواند کاربر را به HomeFragment هدایت کند. این را می توان با انتشار یک شی SignInState.SignedIn در SharedFlow با نام signInStateMutable انجام داد. همچنین می‌خواهیم refreshCredentials برای واکشی اعتبار کاربر فراخوانی کنیم تا در UI فهرست شوند.

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)
  }
}

برنامه را اجرا کنید و روی "Reauth" کلیک کنید تا AuthFragment باز شود. اکنون باید یک گفتگوی اثر انگشت را مشاهده کنید که از شما می خواهد با اثر انگشت خود وارد شوید.

45f81419f84952c8.png

تبریک میگم اکنون یاد گرفتید که چگونه از FIDO2 API در اندروید برای ثبت نام و ورود به سیستم استفاده کنید.

7. تبریک می گویم!

شما با موفقیت لبه کد را به پایان رساندید - اولین API FIDO2 Android شما .

چیزی که یاد گرفتی

  • نحوه ثبت اعتبار با استفاده از تأیید کننده پلتفرم تأیید کننده کاربر.
  • چگونه یک کاربر را با استفاده از یک احراز هویت ثبت شده احراز هویت کنیم.
  • گزینه های موجود برای ثبت احراز هویت جدید.
  • بهترین شیوه های UX برای احراز هویت مجدد با استفاده از حسگر بیومتریک.

مرحله بعدی

  • یاد بگیرید که چگونه تجربه مشابهی را در یک وب سایت ایجاد کنید.

شما می توانید آن را با امتحان کردن اولین آزمایشگاه کد WebAuthn خود یاد بگیرید!

منابع

تشکر ویژه از Yuriy Ackermann از FIDO Alliance برای کمک شما.