Skip to content

Commit 2ec372c

Browse files
committed
Supporting passkey via AuthenticationAPIClient
1 parent 9e28b45 commit 2ec372c

File tree

13 files changed

+424
-241
lines changed

13 files changed

+424
-241
lines changed

auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt

Lines changed: 73 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import com.auth0.android.request.internal.ResponseUtils.isNetworkError
1212
import com.auth0.android.result.Challenge
1313
import com.auth0.android.result.Credentials
1414
import com.auth0.android.result.DatabaseUser
15-
import com.auth0.android.result.PasskeyChallengeResponse
16-
import com.auth0.android.result.PasskeyRegistrationResponse
15+
import com.auth0.android.result.PasskeyChallenge
16+
import com.auth0.android.result.PasskeyRegistrationChallenge
1717
import com.auth0.android.result.UserProfile
1818
import com.google.gson.Gson
1919
import okhttp3.HttpUrl.Companion.toHttpUrl
@@ -155,25 +155,39 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
155155

156156

157157
/**
158-
* Log in a user using passkeys.
159-
* This should be called after the client has received the Passkey challenge and Auth-session from the server .
158+
* Sign-in a user using passkeys.
159+
* This should be called after the client has received the passkey challenge and auth-session from the server
160+
* The default scope used is 'openid profile email'.
161+
*
160162
* Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types)
161163
* to learn how to enable it.
162164
*
163-
* @param authSession the auth session received from the server as part of the public challenge request.
164-
* @param authResponse the public key credential response to be sent to the server
165-
* @param parameters additional parameters to be sent as part of the request
165+
* Example usage:
166+
*
167+
* ```
168+
* client.signinWithPasskey("{authSession}", "{authResponse}","{realm}")
169+
* .validateClaims() //mandatory
170+
* .addParameter("scope","scope")
171+
* .start(object: Callback<Credentials, AuthenticationException> {
172+
* override fun onFailure(error: AuthenticationException) { }
173+
* override fun onSuccess(result: Credentials) { }
174+
* })
175+
* ```
176+
*
177+
* @param authSession the auth session received from the server as part of the public key challenge request.
178+
* @param authResponse the public key credential authentication response
179+
* @param realm the default connection to use
166180
* @return a request to configure and start that will yield [Credentials]
167181
*/
168-
internal fun signinWithPasskey(
182+
public fun signinWithPasskey(
169183
authSession: String,
170-
authResponse: PublicKeyCredentialResponse,
171-
parameters: Map<String, String>
184+
authResponse: PublicKeyCredentials,
185+
realm: String
172186
): AuthenticationRequest {
173187
val params = ParameterBuilder.newBuilder().apply {
174188
setGrantType(ParameterBuilder.GRANT_TYPE_PASSKEY)
175189
set(AUTH_SESSION_KEY, authSession)
176-
addAll(parameters)
190+
setRealm(realm)
177191
}.asDictionary()
178192

179193
return loginWithToken(params)
@@ -185,64 +199,86 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
185199

186200

187201
/**
188-
* Register a user and returns a challenge.
202+
* Sign-up a user and returns a challenge for private and public key generation.
203+
* The default scope used is 'openid profile email'.
189204
* Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types)
190205
* to learn how to enable it.
191206
*
192-
* @param userMetadata user information of the client
193-
* @param parameters additional parameter to be sent as part of the request
194-
* @return a request to configure and start that will yield [PasskeyRegistrationResponse]
207+
* Example usage:
208+
*
209+
*
210+
* ```
211+
* client.signupWithPasskey("{userData}","{realm}")
212+
* .addParameter("scope","scope")
213+
* .start(object: Callback<PasskeyRegistration, AuthenticationException> {
214+
* override fun onSuccess(result: PasskeyRegistration) { }
215+
* override fun onFailure(error: AuthenticationException) { }
216+
* })
217+
* ```
218+
*
219+
* @param userData user information of the client
220+
* @param realm default connection to use
221+
* @return a request to configure and start that will yield [PasskeyRegistrationChallenge]
195222
*/
196-
internal fun signupWithPasskey(
197-
userMetadata: UserMetadataRequest,
198-
parameters: Map<String, String>,
199-
): Request<PasskeyRegistrationResponse, AuthenticationException> {
200-
val user = Gson().toJsonTree(userMetadata)
223+
public fun signupWithPasskey(
224+
userData: UserData,
225+
realm: String
226+
): Request<PasskeyRegistrationChallenge, AuthenticationException> {
227+
val user = Gson().toJsonTree(userData)
201228
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
202229
.addPathSegment(PASSKEY_PATH)
203230
.addPathSegment(REGISTER_PATH)
204231
.build()
205232

206233
val params = ParameterBuilder.newBuilder().apply {
207234
setClientId(clientId)
208-
parameters[ParameterBuilder.REALM_KEY]?.let {
209-
setRealm(it)
210-
}
235+
setRealm(realm)
211236
}.asDictionary()
212237

213-
val passkeyRegistrationAdapter: JsonAdapter<PasskeyRegistrationResponse> = GsonAdapter(
214-
PasskeyRegistrationResponse::class.java, gson
215-
)
216-
val post = factory.post(url.toString(), passkeyRegistrationAdapter)
217-
.addParameters(params) as BaseRequest<PasskeyRegistrationResponse, AuthenticationException>
238+
val passkeyRegistrationChallengeAdapter: JsonAdapter<PasskeyRegistrationChallenge> =
239+
GsonAdapter(
240+
PasskeyRegistrationChallenge::class.java, gson
241+
)
242+
val post = factory.post(url.toString(), passkeyRegistrationChallengeAdapter)
243+
.addParameters(params) as BaseRequest<PasskeyRegistrationChallenge, AuthenticationException>
218244
post.addParameter(USER_PROFILE_KEY, user)
219245
return post
220246
}
221247

222248

223249
/**
224-
* Request for a challenge to initiate a passkey login flow
250+
* Request for a challenge to initiate passkey login flow
225251
* Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types)
226252
* to learn how to enable it.
227253
*
228-
* @param realm An optional connection name
229-
* @return a request to configure and start that will yield [PasskeyChallengeResponse]
254+
* Example usage:
255+
*
256+
* ```
257+
* client.passkeyChallenge("{realm}")
258+
* .start(object: Callback<PasskeyChallenge, AuthenticationException> {
259+
* override fun onSuccess(result: PasskeyChallenge) { }
260+
* override fun onFailure(error: AuthenticationException) { }
261+
* })
262+
* ```
263+
*
264+
* @param realm A default connection name
265+
* @return a request to configure and start that will yield [PasskeyChallenge]
230266
*/
231-
internal fun passkeyChallenge(
232-
realm: String?
233-
): Request<PasskeyChallengeResponse, AuthenticationException> {
267+
public fun passkeyChallenge(
268+
realm: String
269+
): Request<PasskeyChallenge, AuthenticationException> {
234270
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
235271
.addPathSegment(PASSKEY_PATH)
236272
.addPathSegment(CHALLENGE_PATH)
237273
.build()
238274

239275
val parameters = ParameterBuilder.newBuilder().apply {
240276
setClientId(clientId)
241-
realm?.let { setRealm(it) }
277+
setRealm(realm)
242278
}.asDictionary()
243279

244-
val passkeyChallengeAdapter: JsonAdapter<PasskeyChallengeResponse> = GsonAdapter(
245-
PasskeyChallengeResponse::class.java, gson
280+
val passkeyChallengeAdapter: JsonAdapter<PasskeyChallenge> = GsonAdapter(
281+
PasskeyChallenge::class.java, gson
246282
)
247283

248284
return factory.post(url.toString(), passkeyChallengeAdapter)

auth0/src/main/java/com/auth0/android/provider/PasskeyAuthProvider.kt

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.auth0.android.authentication.AuthenticationAPIClient
99
import com.auth0.android.authentication.AuthenticationException
1010
import com.auth0.android.authentication.ParameterBuilder
1111
import com.auth0.android.callback.Callback
12-
import com.auth0.android.request.UserMetadataRequest
12+
import com.auth0.android.request.UserData
1313
import com.auth0.android.result.Credentials
1414
import java.util.concurrent.Executor
1515
import java.util.concurrent.Executors
@@ -99,12 +99,12 @@ public object PasskeyAuthProvider {
9999
callback.onFailure(ex)
100100
return
101101
}
102-
val passkeyManager =
103-
PasskeyManager(
104-
AuthenticationAPIClient(auth0),
105-
CredentialManager.create(context)
106-
)
107-
passkeyManager.signin(context, parameters, callback, executor)
102+
val passkeyManager = PasskeyManager(
103+
AuthenticationAPIClient(auth0), CredentialManager.create(context)
104+
)
105+
passkeyManager.signin(
106+
context, parameters[ParameterBuilder.REALM_KEY]!!, parameters, callback, executor
107+
)
108108
}
109109
}
110110

@@ -211,14 +211,17 @@ public object PasskeyAuthProvider {
211211
callback.onFailure(ex)
212212
return
213213
}
214-
val passkeyManager =
215-
PasskeyManager(
216-
AuthenticationAPIClient(auth0),
217-
CredentialManager.create(context)
218-
)
219-
val userMetadata = UserMetadataRequest(email, phoneNumber, username, name)
214+
val passkeyManager = PasskeyManager(
215+
AuthenticationAPIClient(auth0), CredentialManager.create(context)
216+
)
217+
val userData = UserData(email, phoneNumber, username, name)
220218
passkeyManager.signup(
221-
context, userMetadata, parameters, callback, executor
219+
context,
220+
userData,
221+
parameters[ParameterBuilder.REALM_KEY]!!,
222+
parameters,
223+
callback,
224+
executor
222225
)
223226
}
224227
}

auth0/src/main/java/com/auth0/android/provider/PasskeyManager.kt

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ import com.auth0.android.authentication.AuthenticationAPIClient
2828
import com.auth0.android.authentication.AuthenticationException
2929
import com.auth0.android.authentication.ParameterBuilder
3030
import com.auth0.android.callback.Callback
31-
import com.auth0.android.request.PublicKeyCredentialResponse
32-
import com.auth0.android.request.UserMetadataRequest
31+
import com.auth0.android.request.PublicKeyCredentials
32+
import com.auth0.android.request.UserData
3333
import com.auth0.android.result.Credentials
34-
import com.auth0.android.result.PasskeyChallengeResponse
35-
import com.auth0.android.result.PasskeyRegistrationResponse
34+
import com.auth0.android.result.PasskeyChallenge
35+
import com.auth0.android.result.PasskeyRegistrationChallenge
3636
import com.google.gson.Gson
3737
import java.util.concurrent.Executor
3838
import java.util.concurrent.Executors
@@ -49,15 +49,17 @@ internal class PasskeyManager(
4949
@SuppressLint("PublicKeyCredential")
5050
fun signup(
5151
context: Context,
52-
userMetadata: UserMetadataRequest,
52+
userData: UserData,
53+
realm: String,
5354
parameters: Map<String, String>,
5455
callback: Callback<Credentials, AuthenticationException>,
5556
executor: Executor = Executors.newSingleThreadExecutor()
5657
) {
5758

58-
authenticationAPIClient.signupWithPasskey(userMetadata, parameters)
59-
.start(object : Callback<PasskeyRegistrationResponse, AuthenticationException> {
60-
override fun onSuccess(result: PasskeyRegistrationResponse) {
59+
authenticationAPIClient.signupWithPasskey(userData, realm)
60+
.addParameters(parameters)
61+
.start(object : Callback<PasskeyRegistrationChallenge, AuthenticationException> {
62+
override fun onSuccess(result: PasskeyRegistrationChallenge) {
6163
val pasKeyRegistrationResponse = result
6264
val request = CreatePublicKeyCredentialRequest(
6365
Gson().toJson(
@@ -83,12 +85,16 @@ internal class PasskeyManager(
8385
response = result as CreatePublicKeyCredentialResponse
8486
val authRequest = Gson().fromJson(
8587
response?.registrationResponseJson,
86-
PublicKeyCredentialResponse::class.java
88+
PublicKeyCredentials::class.java
8789
)
90+
8891
authenticationAPIClient.signinWithPasskey(
89-
pasKeyRegistrationResponse.authSession, authRequest, parameters
92+
pasKeyRegistrationResponse.authSession,
93+
authRequest,
94+
realm
9095
)
9196
.validateClaims()
97+
.addParameters(parameters)
9298
.start(callback)
9399
}
94100
})
@@ -106,13 +112,14 @@ internal class PasskeyManager(
106112
@RequiresApi(api = Build.VERSION_CODES.P)
107113
fun signin(
108114
context: Context,
115+
realm: String,
109116
parameters: Map<String, String>,
110117
callback: Callback<Credentials, AuthenticationException>,
111118
executor: Executor = Executors.newSingleThreadExecutor()
112119
) {
113-
authenticationAPIClient.passkeyChallenge(parameters[ParameterBuilder.REALM_KEY])
114-
.start(object : Callback<PasskeyChallengeResponse, AuthenticationException> {
115-
override fun onSuccess(result: PasskeyChallengeResponse) {
120+
authenticationAPIClient.passkeyChallenge(realm)
121+
.start(object : Callback<PasskeyChallenge, AuthenticationException> {
122+
override fun onSuccess(result: PasskeyChallenge) {
116123
val passkeyChallengeResponse = result
117124
val request =
118125
GetPublicKeyCredentialOption(Gson().toJson(passkeyChallengeResponse.authParamsPublicKey))
@@ -135,14 +142,15 @@ internal class PasskeyManager(
135142
is PublicKeyCredential -> {
136143
val authRequest = Gson().fromJson(
137144
credential.authenticationResponseJson,
138-
PublicKeyCredentialResponse::class.java
145+
PublicKeyCredentials::class.java
139146
)
140147
authenticationAPIClient.signinWithPasskey(
141148
passkeyChallengeResponse.authSession,
142149
authRequest,
143-
parameters
150+
realm
144151
)
145152
.validateClaims()
153+
.addParameters(parameters)
146154
.start(callback)
147155
}
148156

auth0/src/main/java/com/auth0/android/request/PublicKeyCredentialRequest.kt renamed to auth0/src/main/java/com/auth0/android/request/PublicKeyCredentials.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.auth0.android.request
33

44
import com.google.gson.annotations.SerializedName
55

6-
internal data class PublicKeyCredentialResponse(
6+
public data class PublicKeyCredentials(
77
@SerializedName("authenticatorAttachment")
88
val authenticatorAttachment: String,
99
@SerializedName("clientExtensionResults")
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.auth0.android.request
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
/**
6+
* User information for registering user when signing up using passkey.
7+
* @param email the email of the user. email can be optional, required, or forbidden depending on the attribute configuration for the database
8+
* @param phoneNumber the phone number of the user. phone number can be optional, required, or forbidden depending on the attribute configuration for the database
9+
* @param userName the username of the user. username can be optional, required, or forbidden depending on the attribute configuration for the database
10+
* @param name optional display name
11+
*/
12+
public data class UserData(
13+
@field:SerializedName("email") val email: String? = null,
14+
@field:SerializedName("phone_number") val phoneNumber: String? = null,
15+
@field:SerializedName("username") val userName: String? = null,
16+
@field:SerializedName("name") val name: String? = null,
17+
)

auth0/src/main/java/com/auth0/android/request/UserMetadataRequest.kt

Lines changed: 0 additions & 13 deletions
This file was deleted.

auth0/src/main/java/com/auth0/android/result/PasskeyChallengeResponse.kt renamed to auth0/src/main/java/com/auth0/android/result/PasskeyChallenge.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ package com.auth0.android.result
33

44
import com.google.gson.annotations.SerializedName
55

6-
internal data class PasskeyChallengeResponse(
6+
/**
7+
* Represents a challenge when user tries to login via passkeys.
8+
*/
9+
public data class PasskeyChallenge(
710
@SerializedName("auth_session")
811
val authSession: String,
912
@SerializedName("authn_params_public_key")
1013
val authParamsPublicKey: AuthParamsPublicKey
1114
)
1215

13-
internal data class AuthParamsPublicKey(
16+
public data class AuthParamsPublicKey(
1417
@SerializedName("challenge")
1518
val challenge: String,
1619
@SerializedName("rpId")

0 commit comments

Comments
 (0)