עם תמיכה במפתחות גישה, כניסה מאוחדת וספקי אימות של צד שלישי, Credential Manager הוא ה-API המומלץ לאימות ב-Android שמספק סביבה מאובטחת ונוחה שמאפשרת למשתמשים לסנכרן ולנהל את פרטי הכניסה שלהם. מפתחים שמשתמשים בפרטי כניסה מקומיים של FIDO2 צריכים לעדכן את האפליקציה שלהם כך שתתמוך באימות באמצעות מפתחות גישה, על ידי שילוב עם Credential Manager API. במאמר הזה מוסבר איך להעביר את הפרויקט מ-FIDO2 ל-Credential Manager.
למה כדאי לעבור מ-FIDO2 ל-Credential Manager
ברוב המקרים, כדאי להעביר את ספק האימות של אפליקציית Android אל Credential Manager. הסיבות למעבר אל Credential Manager כוללות:
- תמיכה במפתחות גישה: Credential Manager תומך במפתחות גישה, מנגנון אימות חדש ללא סיסמה שמאובטח יותר וקל יותר לשימוש מאשר סיסמאות.
- כמה שיטות כניסה: Credential Manager תומך בכמה שיטות כניסה, כולל סיסמאות, מפתחות גישה ושיטות כניסה מאוחדות. כך המשתמשים יכולים לבצע אימות באפליקציה בקלות, בלי קשר לשיטת האימות המועדפת עליהם.
- תמיכה בספקי פרטי כניסה של צד שלישי: ב-Android מגרסה 14 ואילך, Credential Manager תומך בכמה ספקי פרטי כניסה של צד שלישי. כלומר, המשתמשים יכולים להשתמש בפרטי הכניסה הקיימים שלהם מספקים אחרים כדי להיכנס לאפליקציה.
- חוויית משתמש עקבית: Credential Manager מספק חוויית משתמש עקבית יותר לאימות באפליקציות ובמנגנוני כניסה שונים. כך המשתמשים יכולים להבין ולהשתמש בקלות בתהליך האימות של האפליקציה.
כדי להתחיל את ההעברה מ-FIDO2 אל Credential Manager, צריך לבצע את השלבים הבאים.
עדכון יחסי התלות
מעדכנים את הפלאגין של Kotlin בקובץ build.gradle של הפרויקט לגרסה 1.8.10 ואילך.
plugins { //… id 'org.jetbrains.kotlin.android' version '1.8.10' apply false //… }בקובץ
build.gradleשל הפרויקט, מעדכנים את יחסי התלות כך שישתמשו בגרסאות העדכניות של הספריות Credential Manager ו-Play Services Authentication.dependencies { // ... // Credential Manager: implementation 'androidx.credentials:credentials:<latest-version>' // Play Services Authentication: // Optional - needed for credentials support from play services, for devices running // Android 13 and lower: implementation 'androidx.credentials:credentials-play-services-auth:<latest-version>' // ... }מחליפים את ההפעלה של FIDO בהפעלה של Credential Manager. מוסיפים את ההצהרה הזו למחלקה שבה משתמשים כדי ליצור מפתח גישה ולהיכנס לחשבון:
val credMan = CredentialManager.create(context)
יצירת מפתחות גישה
כדי שהמשתמש יוכל להיכנס באמצעות מפתח גישה, צריך ליצור מפתח גישה חדש, לשייך אותו לחשבון של המשתמש ולשמור את המפתח הציבורי של מפתח הגישה בשרת. כדי להגדיר את היכולת הזו באפליקציה, צריך לעדכן את הקריאות להפעלת פונקציות הרישום.
כדי לקבל את הפרמטרים הנדרשים שנשלחים ל-method
createCredential()במהלך יצירת מפתח הגישה, צריך להוסיף אתname("residentKey").value("required")(כפי שמתואר במפרט של WebAuthn) לקריאה לשרתregisterRequest().suspend fun registerRequest() { // ... val call = client.newCall( Builder() .method( "POST", jsonRequestBody { name("attestation").value("none") name("authenticatorSelection").objectValue { name("residentKey").value("required") } } ).build() ) // ... }מגדירים את הסוג של
returnעבורregisterRequest()וכל פונקציות הצאצא לערךJSONObject.suspend fun registerRequest(sessionId: String): ApiResult<JSONObject> { val call = client.newCall( Builder() .url("$BASE_URL/<your api url>") .addHeader("Cookie", formatCookie(sessionId)) .method( "POST", jsonRequestBody { name("attestation").value("none") name("authenticatorSelection").objectValue { name("authenticatorAttachment").value("platform") name("userVerification").value("required") name("residentKey").value("required") } } ).build() ) val response = call.await() return response.result("Error calling the api") { parsePublicKeyCredentialCreationOptions( body ?: throw ApiException("Empty response from the api call") ) } }מסירים בבטחה מהתצוגה את כל השיטות שמטפלות בקריאות ל-intent launcher ול-activity result.
מכיוון שהפונקציה
registerRequest()מחזירה עכשיוJSONObject, אין צורך ליצורPendingIntent. מחליפים את ה-intent שהוחזר ב-JSONObject. מעדכנים את הקריאות למרכז האפליקציות של Intent כדי לקרוא ל-createCredential()מ-Credential Manager API. מפעילים את ה-method createCredential()ב-API.suspend fun createPasskey( activity: Activity, requestResult: JSONObject ): CreatePublicKeyCredentialResponse? { val request = CreatePublicKeyCredentialRequest(requestResult.toString()) var response: CreatePublicKeyCredentialResponse? = null try { response = credMan.createCredential( request = request as CreateCredentialRequest, context = activity ) as CreatePublicKeyCredentialResponse } catch (e: CreateCredentialException) { showErrorAlert(activity, e) return null } return response }אחרי שהקריאה מסתיימת בהצלחה, שולחים את התגובה בחזרה לשרת. הבקשה והתגובה לקריאה הזו דומות להטמעה של FIDO2, ולכן לא נדרשים שינויים.
אימות באמצעות מפתחות גישה
אחרי שמגדירים יצירת מפתחות גישה, אפשר להגדיר את האפליקציה כך שהמשתמשים יוכלו להיכנס ולאמת את הזהות שלהם באמצעות מפתחות הגישה. כדי לעשות את זה, תצטרכו לעדכן את קוד האימות כך שיטפל בתוצאות של Credential Manager, ולהטמיע פונקציה לאימות באמצעות מפתחות גישה.
- הקריאה של בקשת הכניסה לשרת כדי לקבל את המידע הדרוש לשליחה אל הבקשה של
getCredential()זהה להטמעה של FIDO2. לא צריך לשנות שום דבר. בדומה לקריאה לבקשת הרישום, התגובה שמוחזרת היא בפורמט JSONObject.
/** * @param sessionId The session ID to be used for the sign-in. * @param credentialId The credential ID of this device. * @return a JSON object. */ suspend fun signinRequest(): ApiResult<JSONObject> { val call = client.newCall( Builder().url( buildString { append("$BASE_URL/signinRequest") } ).method("POST", jsonRequestBody {}) .build() ) val response = call.await() return response.result("Error calling /signinRequest") { parsePublicKeyCredentialRequestOptions( body ?: throw ApiException("Empty response from /signinRequest") ) } } /** * @param sessionId The session ID to be used for the sign-in. * @param response The JSONObject for signInResponse. * @param credentialId id/rawId. * @return A list of all the credentials registered on the server, * including the newly-registered one. */ suspend fun signinResponse( sessionId: String, response: JSONObject, credentialId: String ): ApiResult<Unit> { val call = client.newCall( Builder().url("$BASE_URL/signinResponse") .addHeader("Cookie", formatCookie(sessionId)) .method( "POST", jsonRequestBody { name("id").value(credentialId) name("type").value(PUBLIC_KEY.toString()) name("rawId").value(credentialId) name("response").objectValue { name("clientDataJSON").value( response.getString("clientDataJSON") ) name("authenticatorData").value( response.getString("authenticatorData") ) name("signature").value( response.getString("signature") ) name("userHandle").value( response.getString("userHandle") ) } } ).build() ) val apiResponse = call.await() return apiResponse.result("Error calling /signingResponse") { } }מסירים בבטחה מהתצוגה את כל השיטות שמטפלות בקריאות ל-intent launcher ולתוצאת הפעילות.
מכיוון ש-
signInRequest()מחזיר עכשיוJSONObject, אין צורך ליצורPendingIntent. מחליפים את ה-intent שמוחזר ב-JSONObject, ומפעילים אתgetCredential()מהשיטות של ה-API.suspend fun getPasskey( activity: Activity, creationResult: JSONObject ): GetCredentialResponse? { Toast.makeText( activity, "Fetching previously stored credentials", Toast.LENGTH_SHORT ) .show() var result: GetCredentialResponse? = null try { val request = GetCredentialRequest( listOf( GetPublicKeyCredentialOption( creationResult.toString(), null ), GetPasswordOption() ) ) result = credMan.getCredential(activity, request) if (result.credential is PublicKeyCredential) { val publicKeycredential = result.credential as PublicKeyCredential Log.i("TAG", "Passkey ${publicKeycredential.authenticationResponseJson}") return result } } catch (e: Exception) { showErrorAlert(activity, e) } return result }אחרי שהקריאה תצליח, שולחים את התשובה חזרה לשרת כדי לאמת את המשתמש. הפרמטרים של הבקשה והתגובה לקריאה הזו ל-API דומים להטמעה של FIDO2, ולכן לא נדרשים שינויים.
מקורות מידע נוספים
- הפניה לדוגמה של Credential Manager
- Codelab של Credential Manager
- איך להוסיף לאפליקציות אימות חלק באמצעות מפתחות גישה דרך Credential Manager API
- Codelab בנושא FIDO2