@@ -12,6 +12,8 @@ import com.auth0.android.request.internal.ResponseUtils.isNetworkError
1212import com.auth0.android.result.Challenge
1313import com.auth0.android.result.Credentials
1414import com.auth0.android.result.DatabaseUser
15+ import com.auth0.android.result.PasskeyChallengeResponse
16+ import com.auth0.android.result.PasskeyRegistrationResponse
1517import com.auth0.android.result.UserProfile
1618import com.google.gson.Gson
1719import okhttp3.HttpUrl.Companion.toHttpUrl
@@ -151,6 +153,102 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
151153 return loginWithToken(parameters)
152154 }
153155
156+
157+ /* *
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 .
160+ * Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types)
161+ * to learn how to enable it.
162+ *
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
166+ * @return a request to configure and start that will yield [Credentials]
167+ */
168+ internal fun signinWithPasskey (
169+ authSession : String ,
170+ authResponse : PublicKeyCredentialResponse ,
171+ parameters : Map <String , String >
172+ ): AuthenticationRequest {
173+ val params = ParameterBuilder .newBuilder().apply {
174+ setGrantType(ParameterBuilder .GRANT_TYPE_PASSKEY )
175+ set(AUTH_SESSION_KEY , authSession)
176+ addAll(parameters)
177+ }.asDictionary()
178+
179+ return loginWithToken(params)
180+ .addParameter(
181+ AUTH_RESPONSE_KEY ,
182+ Gson ().toJsonTree(authResponse)
183+ ) as AuthenticationRequest
184+ }
185+
186+
187+ /* *
188+ * Register a user and returns a challenge.
189+ * Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types)
190+ * to learn how to enable it.
191+ *
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]
195+ */
196+ internal fun signupWithPasskey (
197+ userMetadata : UserMetadataRequest ,
198+ parameters : Map <String , String >,
199+ ): Request <PasskeyRegistrationResponse , AuthenticationException > {
200+ val user = Gson ().toJsonTree(userMetadata)
201+ val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
202+ .addPathSegment(PASSKEY_PATH )
203+ .addPathSegment(REGISTER_PATH )
204+ .build()
205+
206+ val params = ParameterBuilder .newBuilder().apply {
207+ setClientId(clientId)
208+ parameters[ParameterBuilder .REALM_KEY ]?.let {
209+ setRealm(it)
210+ }
211+ }.asDictionary()
212+
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 >
218+ post.addParameter(USER_PROFILE_KEY , user)
219+ return post
220+ }
221+
222+
223+ /* *
224+ * Request for a challenge to initiate a passkey login flow
225+ * Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types)
226+ * to learn how to enable it.
227+ *
228+ * @param realm An optional connection name
229+ * @return a request to configure and start that will yield [PasskeyChallengeResponse]
230+ */
231+ internal fun passkeyChallenge (
232+ realm : String?
233+ ): Request <PasskeyChallengeResponse , AuthenticationException > {
234+ val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
235+ .addPathSegment(PASSKEY_PATH )
236+ .addPathSegment(CHALLENGE_PATH )
237+ .build()
238+
239+ val parameters = ParameterBuilder .newBuilder().apply {
240+ setClientId(clientId)
241+ realm?.let { setRealm(it) }
242+ }.asDictionary()
243+
244+ val passkeyChallengeAdapter: JsonAdapter <PasskeyChallengeResponse > = GsonAdapter (
245+ PasskeyChallengeResponse ::class .java, gson
246+ )
247+
248+ return factory.post(url.toString(), passkeyChallengeAdapter)
249+ .addParameters(parameters)
250+ }
251+
154252 /* *
155253 * Log in a user using an Out Of Band authentication code after they have received the 'mfa_required' error.
156254 * The MFA token tells the server the username or email, password, and realm values sent on the first request.
@@ -695,8 +793,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
695793 val parameters = ParameterBuilder .newBuilder()
696794 .setClientId(clientId)
697795 .setGrantType(ParameterBuilder .GRANT_TYPE_AUTHORIZATION_CODE )
698- .set(OAUTH_CODE_KEY , authorizationCode)
699- .set(REDIRECT_URI_KEY , redirectUri)
796+ .set(OAUTH_CODE_KEY , authorizationCode).set(REDIRECT_URI_KEY , redirectUri)
700797 .set(" code_verifier" , codeVerifier)
701798 .asDictionary()
702799 val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
@@ -736,26 +833,26 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
736833 .addPathSegment(OAUTH_PATH )
737834 .addPathSegment(TOKEN_PATH )
738835 .build()
739- val requestParameters = ParameterBuilder .newBuilder()
740- .setClientId(clientId)
741- .addAll(parameters)
742- .asDictionary()
836+ val requestParameters =
837+ ParameterBuilder .newBuilder()
838+ .setClientId(clientId)
839+ .addAll(parameters)
840+ .asDictionary()
743841 val credentialsAdapter: JsonAdapter <Credentials > = GsonAdapter (
744842 Credentials ::class .java, gson
745843 )
746844 val request = BaseAuthenticationRequest (
747- factory.post(url.toString(), credentialsAdapter),
748- clientId,
749- baseURL
845+ factory.post(url.toString(), credentialsAdapter), clientId, baseURL
750846 )
751847 request.addParameters(requestParameters)
752848 return request
753849 }
754850
755851 private fun profileRequest (): Request <UserProfile , AuthenticationException > {
756- val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
757- .addPathSegment(USER_INFO_PATH )
758- .build()
852+ val url =
853+ auth0.getDomainUrl().toHttpUrl().newBuilder()
854+ .addPathSegment(USER_INFO_PATH )
855+ .build()
759856 val userProfileAdapter: JsonAdapter <UserProfile > = GsonAdapter (
760857 UserProfile ::class .java, gson
761858 )
@@ -782,6 +879,9 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
782879 private const val SUBJECT_TOKEN_KEY = " subject_token"
783880 private const val SUBJECT_TOKEN_TYPE_KEY = " subject_token_type"
784881 private const val USER_METADATA_KEY = " user_metadata"
882+ private const val AUTH_SESSION_KEY = " auth_session"
883+ private const val AUTH_RESPONSE_KEY = " authn_response"
884+ private const val USER_PROFILE_KEY = " user_profile"
785885 private const val SIGN_UP_PATH = " signup"
786886 private const val DB_CONNECTIONS_PATH = " dbconnections"
787887 private const val CHANGE_PASSWORD_PATH = " change_password"
@@ -793,24 +893,23 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
793893 private const val REVOKE_PATH = " revoke"
794894 private const val MFA_PATH = " mfa"
795895 private const val CHALLENGE_PATH = " challenge"
896+ private const val PASSKEY_PATH = " passkey"
897+ private const val REGISTER_PATH = " register"
796898 private const val HEADER_AUTHORIZATION = " Authorization"
797899 private const val WELL_KNOWN_PATH = " .well-known"
798900 private const val JWKS_FILE_PATH = " jwks.json"
799901 private fun createErrorAdapter (): ErrorAdapter <AuthenticationException > {
800902 val mapAdapter = forMap(GsonProvider .gson)
801903 return object : ErrorAdapter <AuthenticationException > {
802904 override fun fromRawResponse (
803- statusCode : Int ,
804- bodyText : String ,
805- headers : Map <String , List <String >>
905+ statusCode : Int , bodyText : String , headers : Map <String , List <String >>
806906 ): AuthenticationException {
807907 return AuthenticationException (bodyText, statusCode)
808908 }
809909
810910 @Throws(IOException ::class )
811911 override fun fromJsonResponse (
812- statusCode : Int ,
813- reader : Reader
912+ statusCode : Int , reader : Reader
814913 ): AuthenticationException {
815914 val values = mapAdapter.fromJson(reader)
816915 return AuthenticationException (values, statusCode)
@@ -819,13 +918,11 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
819918 override fun fromException (cause : Throwable ): AuthenticationException {
820919 if (isNetworkError(cause)) {
821920 return AuthenticationException (
822- " Failed to execute the network request" ,
823- NetworkErrorException (cause)
921+ " Failed to execute the network request" , NetworkErrorException (cause)
824922 )
825923 }
826924 return AuthenticationException (
827- " Something went wrong" ,
828- Auth0Exception (" Something went wrong" , cause)
925+ " Something went wrong" , Auth0Exception (" Something went wrong" , cause)
829926 )
830927 }
831928 }
0 commit comments