גרסה 2024 רבעון 4: איך מפשטים את תהליכי האימות באמצעות Credential Manager API באפליקציה ל-Android

1. לפני שמתחילים

פתרונות אימות מסורתיים גורמים למספר בעיות שקשורות לאבטחה ולנוחות השימוש.

סיסמאות נפוצות מאוד, אבל...

  • קל לשכוח אותם
  • המשתמשים צריכים לדעת איך יוצרים סיסמאות חזקות.
  • קל לפורץ, לאסוף ולשדר מחדש על ידי תוקפים.

הצוות של Android עבד על יצירת Credential Manager API כדי לפשט את חוויית הכניסה ולטפל בסיכוני אבטחה באמצעות תמיכה במפתחות גישה, התקן הבא בתחום לאימות ללא סיסמה.

Credential Manager משלב תמיכה במפתחות גישה עם שיטות אימות מסורתיות כמו סיסמאות, כניסה באמצעות חשבון Google וכו'.

המשתמשים יוכלו ליצור מפתחות גישה, לאחסן אותם במנהל הסיסמאות של Google, שיסנכרן את מפתחות הגישה האלה בין מכשירי Android שבהם המשתמש מחובר לחשבון. כדי שמשתמש יוכל להיכנס באמצעות מפתח גישה, צריך ליצור אותו, לשייך אותו לחשבון משתמש ולשמור את המפתח הציבורי שלו בשרת.

ב-codelab הזה תלמדו איך להירשם באמצעות מפתחות גישה וסיסמה באמצעות Credential Manager API, ולהשתמש בהם לצורכי אימות בעתיד. יש 2 תהליכים, כולל:

  • הרשמה : באמצעות מפתחות גישה וסיסמה.
  • כניסה : באמצעות מפתחות גישה וסיסמה שמורה.

דרישות מוקדמות

  • הבנה בסיסית של אופן הפעלת אפליקציות ב-Android Studio.
  • הבנה בסיסית של תהליך האימות באפליקציות ל-Android.
  • הבנה בסיסית של מפתחות גישה.

מה תלמדו

  • איך יוצרים מפתח גישה.
  • איך שומרים סיסמה במנהל סיסמאות.
  • איך מאמתים משתמשים באמצעות מפתח גישה או סיסמה שמורה.

מה נדרש

אחד משילובי המכשירים הבאים:

  • מכשיר Android עם Android מגרסה 9 ואילך (למפתחות גישה) ו-Android מגרסה 4.4 ואילך(לאימות סיסמה דרך Credential Manager API).
  • מכשיר, רצוי עם חיישן ביומטרי.
  • חשוב לרשום מידע ביומטרי (או נעילת מסך).
  • גרסת הפלאגין של Kotlin : 1.8.10

2. להגדרה

  1. מעתיקים את המאגר הזה למחשב הנייד מההסתעפות credman_codelab : https://github.com/android/identity-samples/tree/credman_codelab
git clone -b credman_codelab https://github.com/android/identity-samples.git
  1. עוברים למודול CredentialManager ופותחים את הפרויקט ב-Android Studio.

מצב ההתחלה של האפליקציה

כדי לראות איך פועל המצב הראשוני של האפליקציה, מבצעים את הפעולות הבאות:

  1. מריצים את האפליקציה.
  2. יוצג מסך ראשי עם לחצן להרשמה ולכניסה. הלחצנים האלה עדיין לא מבצעים פעולה כלשהי, אבל נפעיל את הפונקציונליות שלהם בקטעים הבאים.

7a6fe80f4cf877a8.jpeg

3. הוספת האפשרות להירשם באמצעות מפתחות גישה

כשנרשמים לחשבון חדש באפליקציה ל-Android שמשתמשת ב-Credential Manager API, המשתמשים יכולים ליצור מפתח גישה לחשבון שלהם. מפתח הגישה הזה יישמר באופן מאובטח אצל ספק פרטי הכניסה שבחר המשתמש, וישמש אותו להתחברות עתידית ללא צורך להזין את הסיסמה בכל פעם.

עכשיו עליכם ליצור מפתח גישה ולרשום את פרטי הכניסה של המשתמש באמצעות זיהוי ביומטרי או נעילת מסך.

הרשמה באמצעות מפתח גישה

הקוד בתוך Credential Manager/app/main/java/SignUpFragment.kt מגדיר שדה טקסט 'username' ולחצן להרשמה באמצעות מפתח גישה.

1f4c50daa2551f1.jpeg

מעבירים את האתגר ואת תגובת ה-JSON האחרת לקריאה של createPasskey()‎

לפני שיוצרים מפתח גישה, צריך לבקש מהשרת את המידע הנדרש כדי להעביר אותו ל-Credential Manager API במהלך הקריאה createCredential().

כבר יש לכם תגובה מדומה בנכסים של הפרויקט, שנקראת RegFromServer.txt, שמחזירה את הפרמטרים הנדרשים ב-codelab הזה.

  • באפליקציה, עוברים אל SignUpFragment.kt, מחפשים את השיטה signUpWithPasskeys שבה כותבים את הלוגיקה ליצירת מפתח גישה ולכניסת המשתמש. אפשר למצוא את השיטה באותה כיתה.
  • בודקים את הבלוק else עם תגובה כדי לקרוא ל-createPasskey() ומחליפים אותו בקוד הבא:

SignUpFragment.kt

//TODO : Call createPasskey() to signup with passkey

val data = createPasskey()

ה-method הזה ייקרא אחרי שתזינו שם משתמש תקין במסך.

  • בתוך השיטה createPasskey(), צריך ליצור CreatePublicKeyCredentialRequest() עם הפרמטרים הנדרשים שמוחזרים.

SignUpFragment.kt

//TODO create a CreatePublicKeyCredentialRequest() with necessary registration json from server

val request = CreatePublicKeyCredentialRequest(fetchRegistrationJsonFromServer())

השיטה fetchRegistrationJsonFromServer() קוראת תגובת JSON של שרת PublicKeyCredentialCreationOptions מהנכסים ומחזירה את קובץ ה-JSON של הרישום שצריך להעביר בזמן יצירת מפתח הגישה.

  • מחפשים את השיטה fetchRegistrationJsonFromServer() ומחליפים את TODO בקוד הבא כדי להחזיר JSON, וגם מסירים את משפט ההחזרה של המחרוזת הריקה :

SignUpFragment.kt

//TODO fetch registration mock response

val response = requireContext().readFromAsset("RegFromServer")

//Update userId,challenge, name and Display name in the mock
return response.replace("<userId>", getEncodedUserId())
   .replace("<userName>", binding.username.text.toString())
   .replace("<userDisplayName>", binding.username.text.toString())
   .replace("<challenge>", getEncodedChallenge())
  • קובץ ה-JSON הזה חלקי ויש בו 4 שדות שצריך להחליף.
  • המזהה UserId צריך להיות ייחודי כדי שמשתמש יוכל ליצור כמה מפתחות גישה (אם יש צורך). מחליפים את <userId> בערך userId שנוצר.
  • גם השדה <challenge> צריך להיות ייחודי, כדי שתיווצר לכם משימת אימות ייחודית אקראית. השיטה כבר נמצאת בקוד.

תשובה אמיתית של שרת PublicKeyCredentialCreationOptions עשויה להחזיר אפשרויות נוספות. בהמשך מוצגת דוגמה לחלק מהשדות האלה:

{
  "challenge": String,
  "rp": {
    "name": String,
    "id": String
  },
  "user": {
    "id": String,
    "name": String,
    "displayName": String
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

בטבלה הבאה מוסבר על חלק מהפרמטרים החשובים באובייקט PublicKeyCredentialCreationOptions:

פרמטרים

תיאורים

challenge

מחרוזת אקראית שנוצרה על ידי השרת ומכילה מספיק אנטרופיה כדי שלא ניתן יהיה לנחש אותה. האורך המינימלי שלו הוא 16 בייטים. השדה הזה נדרש, אבל לא נעשה בו שימוש במהלך הרישום, אלא אם מבצעים אימות.

user.id

המזהה הייחודי של המשתמש. הערך הזה לא יכול לכלול פרטים אישיים מזהים, למשל כתובות אימייל או שמות משתמשים. ערך אקראי באורך 16 בייטים שנוצר לכל חשבון יתאים.

user.name

השדה הזה צריך להכיל מזהה ייחודי של החשבון שהמשתמש יזהה, כמו כתובת האימייל או שם המשתמש שלו. השם יופיע בבורר החשבונות. (אם משתמשים בשם משתמש, צריך להשתמש באותו ערך כמו באימות באמצעות סיסמה).

user.displayName

השדה הזה הוא שם אופציונלי וידידותי יותר למשתמש של החשבון.

rp.id

הישות של הצד הנסמך תואמת לפרטי האפליקציה שלכם. יש לו את המאפיינים הבאים:

  • name (חובה): שם האפליקציה
  • ID (אופציונלי): תואם לדומיין או לתת-הדומיין. אם השדה הזה לא מופיע, המערכת משתמשת בדומיין הנוכחי.
  • icon (אופציונלי).

pubKeyCredParams

רשימה של סוגי מפתחות ואלגוריתמים מותרים. הרשימה הזו חייבת להכיל לפחות רכיב אחד.

excludeCredentials

יכול להיות שהמשתמש שמנסה לרשום מכשיר רשם מכשירים אחרים. כדי להגביל את היצירה של כמה פרטי כניסה לאותו חשבון במאמת חשבונות אחד, אפשר להתעלם מהמכשירים האלה. אם הרכיב transports מסופק, הוא צריך להכיל את התוצאה של קריאה ל-getTransports() במהלך הרישום של כל פרטי הכניסה.

authenticatorSelection.authenticatorAttachment

הערך הזה מציין אם צריך לחבר את המכשיר לפלטפורמה, אם לא צריך או אם אין צורך בכך. מגדירים את הערך הזה כ-platform. המשמעות היא שרוצים אימות שמוטמע במכשיר הפלטפורמה, והמשתמש לא יתבקש להכניס, למשל, מפתח אבטחה מסוג USB.

residentKey

מציינים את הערך required כדי ליצור מפתח גישה.

יצירת פרטי כניסה

  1. אחרי שיוצרים CreatePublicKeyCredentialRequest(), צריך לבצע קריאה ל-createCredential() עם הבקשה שנוצרה.

SignUpFragment.kt

//TODO call createCredential() with createPublicKeyCredentialRequest

try {
   response = credentialManager.createCredential(
       requireActivity(),
       request
   ) as CreatePublicKeyCredentialResponse
} catch (e: CreateCredentialException) {
   configureProgress(View.INVISIBLE)
   handlePasskeyFailure(e)
}
  • מעבירים את המידע הנדרש אל createCredential().
  • לאחר שהבקשה תאושר, יוצג במסך גיליון תחתון עם בקשה ליצור מפתח גישה.
  • עכשיו המשתמשים יכולים לאמת את הזהות שלהם באמצעות נתונים ביומטריים או נעילת מסך וכו'.
  • אתם מטפלים בחשיפה של התצוגות המוצגות (rendered) ובחריגות אם הבקשה נכשלת או לא מצליחה מסיבה כלשהי. כאן מתועדות הודעות השגיאה ומוצגות באפליקציה בתיבת דו-שיח עם הודעת שגיאה. אפשר לבדוק את יומני השגיאות המלאים דרך Android Studio או באמצעות הפקודה adb debug.

1ea8ace66135de1e.png

  1. לבסוף, צריך להשלים את תהליך הרישום. האפליקציה שולחת פרטי כניסה של מפתח ציבורי לשרת, שמרשם אותו למשתמש הנוכחי.

כאן השתמשנו בשרת מדומה, ולכן אנחנו פשוט מחזירים את הערך true כדי לציין שהשרת שמר את המפתח הציבורי הרשום לצורכי אימות ואימות עתידיים. מידע נוסף על הרשמה של מפתחות גישה בצד השרת זמין להטמעה שלכם.

בתוך השיטה signUpWithPasskeys(), מחפשים את התגובה הרלוונטית ומחליפים אותה בקוד הבא:

SignUpFragment.kt

//TODO : complete the registration process after sending public key credential to your server and let the user in

data?.let {
   registerResponse()
   DataProvider.setSignedInThroughPasskeys(true)
   listener.showHome()
}
  • הפונקציה registerResponse() מחזירה את הערך true, שמציין שהמפתח הציבורי נשמר בשרת הדמה לשימוש עתידי.
  • מגדירים את הדגל setSignedInThroughPasskeys לערך true.
  • אחרי שהמשתמש מתחבר, מפנים אותו למסך הבית.

PublicKeyCredential אמיתי עשוי להכיל שדות נוספים. דוגמה לשדות האלה מוצגת בהמשך:

{
  "id": String,
  "rawId": String,
  "type": "public-key",
  "response": {
    "clientDataJSON": String,
    "attestationObject": String,
  }
}

בטבלה הבאה מוסבר על חלק מהפרמטרים החשובים באובייקט PublicKeyCredential:

פרמטרים

תיאורים

id

מזהה בקידוד Base64URL של מפתח הגישה שנוצר. המזהה הזה עוזר לדפדפן לקבוע אם מפתח גישה תואם נמצא במכשיר במהלך האימות. צריך לאחסן את הערך הזה במסד הנתונים בקצה העורפי.

rawId

גרסה של אובייקט ArrayBuffer של מזהה פרטי הכניסה.

response.clientDataJSON

נתוני לקוח שמקודדים באובייקט ArrayBuffer.

response.attestationObject

אובייקט אימות בקידוד ArrayBuffer. הוא מכיל מידע חשוב, כמו מזהה RP, דגלים ומפתח ציבורי.

מריצים את האפליקציה, לוחצים על הלחצן Sign up with passkeys (הרשמה באמצעות מפתחות גישה) ויוצרים מפתח גישה.

4. שמירת סיסמה בספק פרטי הכניסה

באפליקציה הזו, במסך SignUp (הרשמה), כבר הופעלה הרשמה עם שם משתמש וסיסמה למטרות הדגמה.

כדי לשמור את פרטי הכניסה של הסיסמה של המשתמש אצל ספק הסיסמאות שלו, צריך להטמיע CreatePasswordRequest כדי להעביר ל-createCredential() כדי לשמור את הסיסמה.

  • מחפשים את השיטה signUpWithPassword(), מחליפים את TODO בקריאה ל-createPassword:

SignUpFragment.kt

//TODO : Save the user credential password with their password provider

createPassword()
  • בתוך השיטה createPassword(), צריך ליצור בקשה לקבלת סיסמה באופן הבא, מחליפים את TODO בקוד הבא:

SignUpFragment.kt

//TODO : CreatePasswordRequest with entered username and password

val request = CreatePasswordRequest(
   binding.username.text.toString(),
   binding.password.text.toString()
)
  • לאחר מכן, בתוך השיטה createPassword(), יוצרים פרטי כניסה באמצעות בקשה ליצירת סיסמה ושומרים את פרטי הכניסה של סיסמת המשתמש אצל ספק הסיסמאות שלו. מחליפים את TODO בקוד הבא:

SignUpFragment.kt

//TODO : Create credential with created password request


try {
   credentialManager.createCredential(requireActivity(), request) as CreatePasswordResponse
} catch (e: Exception) {
   Log.e("Auth", " Exception Message : " + e.message)
}
  • עכשיו שמרתם את פרטי הכניסה של הסיסמה אצל ספק הסיסמאות של המשתמש, כדי לבצע אימות באמצעות סיסמה בלחיצה אחת בלבד.

5. הוספת היכולת לבצע אימות באמצעות מפתח גישה או סיסמה

עכשיו אתם מוכנים להשתמש בו כדי לבצע אימות באפליקציה בצורה בטוחה.

76e81460b26f9798.png

אחזור האתגר ואפשרויות אחרות להעברה לקריאה של getPasskey()‎

לפני שמבקשים מהמשתמש לבצע אימות, צריך לבקש פרמטרים להעברה ב-JSON של WebAuthn מהשרת, כולל אתגר.

כבר יש לכם תגובה מדומה בנכסים (AuthFromServer.txt) שמחזירה פרמטרים כאלה ב-codelab הזה.

  • באפליקציה, עוברים אל SignInFragment.kt, מחפשים את השיטה signInWithSavedCredentials שבה כותבים את הלוגיקה לאימות באמצעות מפתח גישה או סיסמה שמורים, ומאפשרים למשתמש להיכנס:
  • בודקים את הבלוק else עם תגובה כדי לקרוא ל-createPasskey() ומחליפים אותו בקוד הבא:

SignInFragment.kt

//TODO : Call getSavedCredentials() method to signin using passkey/password

val data = getSavedCredentials()
  • בתוך השיטה getSavedCredentials(), צריך ליצור GetPublicKeyCredentialOption() עם הפרמטרים הנדרשים כדי לקבל פרטי כניסה מספק פרטי הכניסה.

SigninFragment.kt

//TODO create a GetPublicKeyCredentialOption() with necessary registration json from server

val getPublicKeyCredentialOption =
   GetPublicKeyCredentialOption(fetchAuthJsonFromServer(), null)

השיטה fetchAuthJsonFromServer() קוראת את תגובת ה-JSON של האימות מהנכסים ומחזירה JSON של אימות כדי לאחזר את כל מפתחות הגישה שמשויכים לחשבון המשתמש הזה.

הפרמטר השני של GetPublicKeyCredentialOption() הוא clientDataHash – גיבוב שמשמש לאימות הזהות של הצד הנסמך. מגדירים את השדה הזה רק אם הגדרתם את GetCredentialRequest.origin. באפליקציית הדוגמה, הערך מוגדר כ-null.

  • מחפשים את השיטה fetchAuthJsonFromServer() ומחליפים את TODO בקוד הבא כדי להחזיר JSON, וגם מסירים את משפט ההחזרה של המחרוזת הריקה:

SignInFragment.kt

//TODO fetch authentication mock json

return requireContext().readFromAsset("AuthFromServer")

הערה : השרת של סדנת הקוד הזו מתוכנן להחזיר קובץ JSON שדומה ככל האפשר למילון PublicKeyCredentialRequestOptions שמוענק לקריאה getCredential()‎ של ה-API. קטע הקוד הבא כולל כמה דוגמאות לאפשרויות שעשויות להופיע בתגובה אמיתית:

{
  "challenge": String,
  "rpId": String,
  "userVerification": "",
  "timeout": 1800000
}

בטבלה הבאה מוסבר על חלק מהפרמטרים החשובים באובייקט PublicKeyCredentialRequestOptions:

פרמטרים

תיאורים

challenge

אתגר שנוצר על ידי השרת באובייקט ArrayBuffer. הפעולה הזו נדרשת כדי למנוע התקפות שליחה מחדש. לעולם אל תאשרו את אותו אתגר פעמיים בתשובה. אפשר להתייחס אליו כאל אסימון CSRF.

rpId

מזהה RP הוא דומיין. אתר יכול לציין את הדומיין שלו או סיומת שניתן לרשום. הערך הזה חייב להתאים לפרמטר rp.id ששימש ליצירת מפתח הגישה.

  • בשלב הבא צריך ליצור אובייקט PasswordOption() כדי לאחזר את כל הסיסמאות השמורות בספק הסיסמאות דרך Credential Manager API עבור חשבון המשתמש הזה. בתוך השיטה getSavedCredentials(), מחפשים את TODO ומחליפים אותו בקוד הבא:

SigninFragment.kt

//TODO create a PasswordOption to retrieve all the associated user's password

val getPasswordOption = GetPasswordOption()

אחזור פרטי הכניסה

  • בשלב הבא, צריך לבצע קריאה לבקשה getCredential() עם כל האפשרויות שלמעלה כדי לאחזר את פרטי הכניסה המשויכים:

SignInFragment.kt

//TODO call getCredential() with required credential options

val result = try {
   credentialManager.getCredential(
       requireActivity(),
       GetCredentialRequest(
           listOf(
               getPublicKeyCredentialOption,
               getPasswordOption
           )
     )
   )
} catch (e: Exception) {
   configureViews(View.INVISIBLE, true)
   Log.e("Auth", "getCredential failed with exception: " + e.message.toString())
   activity?.showErrorAlert(
       "An error occurred while authenticating through saved credentials. Check logs for additional details"
   )
   return null
}

if (result.credential is PublicKeyCredential) {
   val cred = result.credential as PublicKeyCredential
   DataProvider.setSignedInThroughPasskeys(true)
   return "Passkey: ${cred.authenticationResponseJson}"
}
if (result.credential is PasswordCredential) {
   val cred = result.credential as PasswordCredential
   DataProvider.setSignedInThroughPasskeys(false)
   return "Got Password - User:${cred.id} Password: ${cred.password}"
}
if (result.credential is CustomCredential) {
   //If you are also using any external sign-in libraries, parse them here with the utility functions provided.
}

  • מעבירים את המידע הנדרש אל getCredential(). המערכת משתמשת ברשימת האפשרויות של פרטי הכניסה ובהקשר של הפעילות כדי להציג את האפשרויות בגיליון התחתון בהקשר הזה.
  • לאחר שהבקשה תאושר, יופיע בחלק התחתון של המסך גיליון שבו יפורטו כל פרטי הכניסה שנוצרו לחשבון המשויך.
  • עכשיו המשתמשים יכולים לאמת את הזהות שלהם באמצעות מידע ביומטרי או שיטה לביטול נעילת המסך וכו' כדי לאמת את פרטי הכניסה שנבחרו.
  • אם פרטי הכניסה שנבחרו הם PublicKeyCredential, מגדירים את הדגל setSignedInThroughPasskeys כ-true. אחרת, מגדירים אותו כ-false.

קטע הקוד הבא כולל אובייקט PublicKeyCredential לדוגמה:

{
  "id": String
  "rawId": String
  "type": "public-key",
  "response": {
    "clientDataJSON": String
    "authenticatorData": String
    "signature": String
    "userHandle": String
  }
}

הטבלה הבאה לא מקיפה, אבל היא מכילה את הפרמטרים החשובים באובייקט PublicKeyCredential:

פרמטרים

תיאורים

id

המזהה בקידוד Base64URL של פרטי הכניסה המאומתים של מפתח הגישה.

rawId

גרסה של אובייקט ArrayBuffer של מזהה פרטי הכניסה.

response.clientDataJSON

אובייקט ArrayBuffer של נתוני לקוח. השדה הזה מכיל מידע, כמו האתגר והמקור ששרת ה-RP צריך לאמת.

response.authenticatorData

אובייקט ArrayBuffer של נתוני מאמת. השדה הזה מכיל מידע כמו מזהה RP.

response.signature

אובייקט ArrayBuffer של החתימה. הערך הזה הוא הליבה של פרטי הכניסה, וצריך לאמת אותו בשרת.

response.userHandle

אובייקט ArrayBuffer שמכיל את מזהה המשתמש שהוגדר בזמן היצירה. אפשר להשתמש בערך הזה במקום במזהה פרטי הכניסה אם השרת צריך לבחור את ערכי המזהה שבהם הוא משתמש, או אם הקצה העורפי רוצה להימנע מהיצירה של אינדקס למזהי פרטי הכניסה.

  • לבסוף, צריך להשלים את תהליך האימות. בדרך כלל, אחרי שהמשתמש משלים את אימות מפתח הגישה, האפליקציה שולחת פרטי כניסה של מפתח ציבורי שמכילים טענת נכוֹנוּת (assertion) לאימות לשרת, שמאמת את טענת הנכוֹנוּת ומאמת את המשתמש.

כאן השתמשנו בשרת מדומה, ולכן אנחנו פשוט מחזירים את הערך true כדי לציין שהשרת אימת את ההצהרה. למידע נוסף על אימות מפתחות גישה בצד השרת, תוכלו להיעזר במאמרים הבאים.

בתוך השיטה signInWithSavedCredentials(), מחפשים את התגובה הרלוונטית ומחליפים אותה בקוד הבא:

SignInFragment.kt

//TODO : complete the authentication process after validating the public key credential to your server and let the user in.

data?.let {
   sendSignInResponseToServer()
   listener.showHome()
}
  • הפונקציה sendSigninResponseToServer() מחזירה את הערך true, שמציין שהשרת (הבדיוני) אימת את המפתח הציבורי לשימוש עתידי.
  • אחרי שהמשתמש מתחבר, מפנים אותו למסך הבית.

מריצים את האפליקציה ועוברים אל כניסה > כניסה באמצעות מפתחות גישה/סיסמה שמורה,ומנסים להיכנס באמצעות פרטי הכניסה השמורים.

רוצה לנסות?

הטמעתם את היצירה של מפתחות גישה, שמירת סיסמה ב-Credential Manager ואימות באמצעות מפתחות גישה או סיסמה שמורה באמצעות Credential Manager API באפליקציה ל-Android.

6. מעולה!

סיימת את הקודלאב הזה! אפשר לבדוק את הפתרון הסופי בכתובת https://github.com/android/identity-samples/tree/main/CredentialManager

אם יש לכם שאלות, אתם יכולים לפרסם אותן ב-StackOverflow עם תג passkey.

מידע נוסף