Skip to content

Commit 7eb5acd

Browse files
authored
Add snippets for creating passkey (#485)
1 parent 56584d4 commit 7eb5acd

File tree

3 files changed

+203
-20
lines changed

3 files changed

+203
-20
lines changed

identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/PasskeyAndPasswordFunctions.kt

+98-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import android.os.Build
2222
import android.os.Bundle
2323
import android.util.Log
2424
import androidx.annotation.RequiresApi
25+
import androidx.credentials.CreatePasswordRequest
26+
import androidx.credentials.CreatePublicKeyCredentialRequest
2527
import androidx.credentials.CredentialManager
2628
import androidx.credentials.CustomCredential
2729
import androidx.credentials.GetCredentialRequest
@@ -30,7 +32,14 @@ import androidx.credentials.GetPasswordOption
3032
import androidx.credentials.GetPublicKeyCredentialOption
3133
import androidx.credentials.PasswordCredential
3234
import androidx.credentials.PublicKeyCredential
35+
import androidx.credentials.exceptions.CreateCredentialCancellationException
36+
import androidx.credentials.exceptions.CreateCredentialCustomException
37+
import androidx.credentials.exceptions.CreateCredentialException
38+
import androidx.credentials.exceptions.CreateCredentialInterruptedException
39+
import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException
40+
import androidx.credentials.exceptions.CreateCredentialUnknownException
3341
import androidx.credentials.exceptions.GetCredentialException
42+
import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException
3443
import kotlinx.coroutines.coroutineScope
3544
import kotlinx.coroutines.runBlocking
3645
import org.json.JSONObject
@@ -43,6 +52,7 @@ class PasskeyAndPasswordFunctions (
4352
// CredentialManager.
4453
private val credentialManager = CredentialManager.create(context)
4554
// [END android_identity_initialize_credman]
55+
private val activityContext = context
4656

4757
// Placeholder for TAG log value.
4858
val TAG = ""
@@ -55,8 +65,7 @@ class PasskeyAndPasswordFunctions (
5565
*/
5666
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
5767
fun signInFlow(
58-
creationResult: JSONObject,
59-
activityContext: Context,
68+
creationResult: JSONObject
6069
) {
6170
val requestJson = creationResult.toString()
6271
// [START android_identity_get_password_passkey_options]
@@ -166,6 +175,93 @@ class PasskeyAndPasswordFunctions (
166175
}
167176
}
168177
// [END android_identity_launch_sign_in_flow_2]
178+
179+
// [START android_identity_create_passkey]
180+
suspend fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) {
181+
val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(
182+
// Contains the request in JSON format. Uses the standard WebAuthn
183+
// web JSON spec.
184+
requestJson = requestJson,
185+
// Defines whether you prefer to use only immediately available
186+
// credentials, not hybrid credentials, to fulfill this request.
187+
// This value is false by default.
188+
preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
189+
)
190+
191+
// Execute CreateCredentialRequest asynchronously to register credentials
192+
// for a user account. Handle success and failure cases with the result and
193+
// exceptions, respectively.
194+
coroutineScope {
195+
try {
196+
val result = credentialManager.createCredential(
197+
// Use an activity-based context to avoid undefined system
198+
// UI launching behavior
199+
context = activityContext,
200+
request = createPublicKeyCredentialRequest,
201+
)
202+
// Handle passkey creation result
203+
} catch (e : CreateCredentialException){
204+
handleFailure(e)
205+
}
206+
}
207+
}
208+
// [END android_identity_create_passkey]
209+
210+
// [START android_identity_handle_create_passkey_failure]
211+
fun handleFailure(e: CreateCredentialException) {
212+
when (e) {
213+
is CreatePublicKeyCredentialDomException -> {
214+
// Handle the passkey DOM errors thrown according to the
215+
// WebAuthn spec.
216+
}
217+
is CreateCredentialCancellationException -> {
218+
// The user intentionally canceled the operation and chose not
219+
// to register the credential.
220+
}
221+
is CreateCredentialInterruptedException -> {
222+
// Retry-able error. Consider retrying the call.
223+
}
224+
is CreateCredentialProviderConfigurationException -> {
225+
// Your app is missing the provider configuration dependency.
226+
// Most likely, you're missing the
227+
// "credentials-play-services-auth" module.
228+
}
229+
is CreateCredentialCustomException -> {
230+
// You have encountered an error from a 3rd-party SDK. If you
231+
// make the API call with a request object that's a subclass of
232+
// CreateCustomCredentialRequest using a 3rd-party SDK, then you
233+
// should check for any custom exception type constants within
234+
// that SDK to match with e.type. Otherwise, drop or log the
235+
// exception.
236+
}
237+
else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
238+
}
239+
}
240+
// [END android_identity_handle_create_passkey_failure]
241+
242+
// [START android_identity_register_password]
243+
suspend fun registerPassword(username: String, password: String) {
244+
// Initialize a CreatePasswordRequest object.
245+
val createPasswordRequest =
246+
CreatePasswordRequest(id = username, password = password)
247+
248+
// Create credential and handle result.
249+
coroutineScope {
250+
try {
251+
val result =
252+
credentialManager.createCredential(
253+
// Use an activity based context to avoid undefined
254+
// system UI launching behavior.
255+
activityContext,
256+
createPasswordRequest
257+
)
258+
// Handle register password result
259+
} catch (e: CreateCredentialException) {
260+
handleFailure(e)
261+
}
262+
}
263+
}
264+
// [END android_identity_register_password]
169265
}
170266

171267
sealed class ExampleCustomCredential {

identity/credentialmanager/src/main/jsonSnippets.json

+87-18
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,98 @@
3838
// [END android_identity_assetlinks_json]
3939
},
4040

41-
// JSON request and response formats
42-
// [START android_identity_format_json_request_passkey]
4341
{
44-
"challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
45-
"allowCredentials": [],
46-
"timeout": 1800000,
47-
"userVerification": "required",
48-
"rpId": "credential-manager-app-test.glitch.me"
42+
"FormatJsonRequestPasskey":
43+
// JSON request format
44+
// [START android_identity_format_json_request_passkey]
45+
{
46+
"challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
47+
"allowCredentials": [],
48+
"timeout": 1800000,
49+
"userVerification": "required",
50+
"rpId": "credential-manager-app-test.glitch.me"
51+
}
52+
// [END android_identity_format_json_request_passkey]
4953
},
50-
// [END android_identity_format_json_request_passkey]
5154

52-
// [START android_identity_format_json_response_passkey]
5355
{
54-
"id": "KEDetxZcUfinhVi6Za5nZQ",
55-
"type": "public-key",
56-
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
57-
"response": {
58-
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
59-
"authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
60-
"signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
61-
"userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
56+
"FormatJsonResponsePasskey":
57+
// JSON response format
58+
// [START android_identity_format_json_response_passkey]
59+
{
60+
"id": "KEDetxZcUfinhVi6Za5nZQ",
61+
"type": "public-key",
62+
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
63+
"response": {
64+
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
65+
"authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
66+
"signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
67+
"userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
68+
}
6269
}
70+
// [END android_identity_format_json_response_passkey]
71+
},
72+
{
73+
"CreatePasskeyJsonRequest":
74+
// Json request for creating a passkey
75+
// [START android_identity_create_passkey_request_json]
76+
{
77+
"challenge": "abc123",
78+
"rp": {
79+
"name": "Credential Manager example",
80+
"id": "credential-manager-test.example.com"
81+
},
82+
"user": {
83+
"id": "def456",
84+
"name": "[email protected]",
85+
"displayName": "[email protected]"
86+
},
87+
"pubKeyCredParams": [
88+
{
89+
"type": "public-key",
90+
"alg": -7
91+
},
92+
{
93+
"type": "public-key",
94+
"alg": -257
95+
}
96+
],
97+
"timeout": 1800000,
98+
"attestation": "none",
99+
"excludeCredentials": [
100+
{
101+
"id": "ghi789",
102+
"type": "public-key"
103+
},
104+
{
105+
"id": "jkl012",
106+
"type": "public-key"
107+
}
108+
],
109+
"authenticatorSelection": {
110+
"authenticatorAttachment": "platform",
111+
"requireResidentKey": true,
112+
"residentKey": "required",
113+
"userVerification": "required"
114+
}
115+
}
116+
// [END android_identity_create_passkey_request_json]
117+
},
118+
{
119+
"CreatePasskeyHandleJsonResponse":
120+
// Json response when creating a passkey
121+
// [START android_identity_create_passkey_response_json]
122+
{
123+
"id": "KEDetxZcUfinhVi6Za5nZQ",
124+
"type": "public-key",
125+
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
126+
"response": {
127+
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
128+
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
129+
}
130+
}
131+
// [END android_identity_create_passkey_response_json]
63132
}
64-
// [END android_identity_format_json_response_passkey]
133+
65134
]
66135
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// [START android_identity_apk_key_hash]
2+
android:apk-key-hash:<sha256_hash-of-apk-signing-cert>
3+
// [END android_identity_apk_key_hash]
4+
5+
// [START android_identity_keytool_sign]
6+
keytool -list -keystore <path-to-apk-signing-keystore>
7+
// [END android_identity_keytool_sign]
8+
9+
// [START android_identity_fingerprint_decode_python]
10+
import binascii
11+
import base64
12+
fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5'
13+
print("android:apk-key-hash:" + base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))
14+
// [END android_identity_fingerprint_decode_python]
15+
16+
// [START android_identity_fingerprint_decoded]
17+
android:apk-key-hash:kffL-daBUxvHpY-4M8yhTavt5QnFEI2LsexohxrGPYU
18+
// [END android_identity_fingerprint_decoded]

0 commit comments

Comments
 (0)