@@ -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
@@ -87,8 +89,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
8789 .set(USERNAME_KEY , usernameOrEmail)
8890 .set(PASSWORD_KEY , password)
8991 .setGrantType(ParameterBuilder .GRANT_TYPE_PASSWORD_REALM )
90- .setRealm(realmOrConnection)
91- .asDictionary()
92+ .setRealm(realmOrConnection).asDictionary()
9293 return loginWithToken(parameters)
9394 }
9495
@@ -151,6 +152,96 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
151152 return loginWithToken(parameters)
152153 }
153154
155+
156+ /* *
157+ * Log in a user using passkeys.
158+ * This should be called after the client has received the Passkey challenge and Auth-session from the server .
159+ * Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types) to learn how to enable it.
160+ *
161+ * @param authSession the auth session received from the server as part of the public challenge request.
162+ * @param authResponse the public key credential response to be sent to the server
163+ * @param parameters additional parameters to be sent as part of the request
164+ * @return a request to configure and start that will yield [Credentials]
165+ */
166+ internal fun signinWithPasskey (
167+ authSession : String ,
168+ authResponse : PublicKeyCredentialResponse ,
169+ parameters : Map <String , String >
170+ ): AuthenticationRequest {
171+ val params = ParameterBuilder .newBuilder().apply {
172+ setGrantType(ParameterBuilder .GRANT_TYPE_PASSKEY )
173+ set(AUTH_SESSION_KEY , authSession)
174+ addAll(parameters)
175+ }.asDictionary()
176+
177+ return loginWithToken(params)
178+ .addParameter(AUTH_RESPONSE_KEY , Gson ().toJsonTree(authResponse)) as AuthenticationRequest
179+ }
180+
181+
182+ /* *
183+ * Register a user and returns a challenge.
184+ * Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types) to learn how to enable it.
185+ *
186+ * @param userMetadata user information of the client
187+ * @param parameters additional parameter to be sent as part of the request
188+ * @return a request to configure and start that will yield [PasskeyRegistrationResponse]
189+ */
190+ internal fun signupWithPasskey (
191+ userMetadata : UserMetadataRequest ,
192+ parameters : Map <String , String >,
193+ ): Request <PasskeyRegistrationResponse , AuthenticationException > {
194+ val user = Gson ().toJsonTree(userMetadata)
195+ val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
196+ .addPathSegment(PASSKEY_PATH )
197+ .addPathSegment(REGISTER_PATH )
198+ .build()
199+
200+ val params = ParameterBuilder .newBuilder().apply {
201+ setClientId(clientId)
202+ parameters[ParameterBuilder .REALM_KEY ]?.let {
203+ setRealm(it)
204+ }
205+ }.asDictionary()
206+
207+ val passkeyRegistrationAdapter: JsonAdapter <PasskeyRegistrationResponse > = GsonAdapter (
208+ PasskeyRegistrationResponse ::class .java, gson
209+ )
210+ val post = factory.post(url.toString(), passkeyRegistrationAdapter)
211+ .addParameters(params) as BaseRequest <PasskeyRegistrationResponse , AuthenticationException >
212+ post.addParameter(USER_PROFILE_KEY , user)
213+ return post
214+ }
215+
216+
217+ /* *
218+ * Request for a challenge to initiate a passkey login flow
219+ * Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types) to learn how to enable it.
220+ *
221+ * @param realm An optional connection name
222+ * @return a request to configure and start that will yield [PasskeyChallengeResponse]
223+ */
224+ internal fun passkeyChallenge (
225+ realm : String?
226+ ): Request <PasskeyChallengeResponse , AuthenticationException > {
227+ val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
228+ .addPathSegment(PASSKEY_PATH )
229+ .addPathSegment(CHALLENGE_PATH )
230+ .build()
231+
232+ val parameters = ParameterBuilder .newBuilder().apply {
233+ setClientId(clientId)
234+ realm?.let { setRealm(it) }
235+ }.asDictionary()
236+
237+ val passkeyChallengeAdapter: JsonAdapter <PasskeyChallengeResponse > = GsonAdapter (
238+ PasskeyChallengeResponse ::class .java, gson
239+ )
240+
241+ return factory.post(url.toString(), passkeyChallengeAdapter)
242+ .addParameters(parameters)
243+ }
244+
154245 /* *
155246 * Log in a user using an Out Of Band authentication code after they have received the 'mfa_required' error.
156247 * The MFA token tells the server the username or email, password, and realm values sent on the first request.
@@ -695,8 +786,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
695786 val parameters = ParameterBuilder .newBuilder()
696787 .setClientId(clientId)
697788 .setGrantType(ParameterBuilder .GRANT_TYPE_AUTHORIZATION_CODE )
698- .set(OAUTH_CODE_KEY , authorizationCode)
699- .set(REDIRECT_URI_KEY , redirectUri)
789+ .set(OAUTH_CODE_KEY , authorizationCode).set(REDIRECT_URI_KEY , redirectUri)
700790 .set(" code_verifier" , codeVerifier)
701791 .asDictionary()
702792 val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
@@ -736,26 +826,26 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
736826 .addPathSegment(OAUTH_PATH )
737827 .addPathSegment(TOKEN_PATH )
738828 .build()
739- val requestParameters = ParameterBuilder .newBuilder()
740- .setClientId(clientId)
741- .addAll(parameters)
742- .asDictionary()
829+ val requestParameters =
830+ ParameterBuilder .newBuilder()
831+ .setClientId(clientId)
832+ .addAll(parameters)
833+ .asDictionary()
743834 val credentialsAdapter: JsonAdapter <Credentials > = GsonAdapter (
744835 Credentials ::class .java, gson
745836 )
746837 val request = BaseAuthenticationRequest (
747- factory.post(url.toString(), credentialsAdapter),
748- clientId,
749- baseURL
838+ factory.post(url.toString(), credentialsAdapter), clientId, baseURL
750839 )
751840 request.addParameters(requestParameters)
752841 return request
753842 }
754843
755844 private fun profileRequest (): Request <UserProfile , AuthenticationException > {
756- val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
757- .addPathSegment(USER_INFO_PATH )
758- .build()
845+ val url =
846+ auth0.getDomainUrl().toHttpUrl().newBuilder()
847+ .addPathSegment(USER_INFO_PATH )
848+ .build()
759849 val userProfileAdapter: JsonAdapter <UserProfile > = GsonAdapter (
760850 UserProfile ::class .java, gson
761851 )
@@ -782,6 +872,9 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
782872 private const val SUBJECT_TOKEN_KEY = " subject_token"
783873 private const val SUBJECT_TOKEN_TYPE_KEY = " subject_token_type"
784874 private const val USER_METADATA_KEY = " user_metadata"
875+ private const val AUTH_SESSION_KEY = " auth_session"
876+ private const val AUTH_RESPONSE_KEY = " authn_response"
877+ private const val USER_PROFILE_KEY = " user_profile"
785878 private const val SIGN_UP_PATH = " signup"
786879 private const val DB_CONNECTIONS_PATH = " dbconnections"
787880 private const val CHANGE_PASSWORD_PATH = " change_password"
@@ -793,24 +886,23 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
793886 private const val REVOKE_PATH = " revoke"
794887 private const val MFA_PATH = " mfa"
795888 private const val CHALLENGE_PATH = " challenge"
889+ private const val PASSKEY_PATH = " passkey"
890+ private const val REGISTER_PATH = " register"
796891 private const val HEADER_AUTHORIZATION = " Authorization"
797892 private const val WELL_KNOWN_PATH = " .well-known"
798893 private const val JWKS_FILE_PATH = " jwks.json"
799894 private fun createErrorAdapter (): ErrorAdapter <AuthenticationException > {
800895 val mapAdapter = forMap(GsonProvider .gson)
801896 return object : ErrorAdapter <AuthenticationException > {
802897 override fun fromRawResponse (
803- statusCode : Int ,
804- bodyText : String ,
805- headers : Map <String , List <String >>
898+ statusCode : Int , bodyText : String , headers : Map <String , List <String >>
806899 ): AuthenticationException {
807900 return AuthenticationException (bodyText, statusCode)
808901 }
809902
810903 @Throws(IOException ::class )
811904 override fun fromJsonResponse (
812- statusCode : Int ,
813- reader : Reader
905+ statusCode : Int , reader : Reader
814906 ): AuthenticationException {
815907 val values = mapAdapter.fromJson(reader)
816908 return AuthenticationException (values, statusCode)
@@ -819,13 +911,11 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
819911 override fun fromException (cause : Throwable ): AuthenticationException {
820912 if (isNetworkError(cause)) {
821913 return AuthenticationException (
822- " Failed to execute the network request" ,
823- NetworkErrorException (cause)
914+ " Failed to execute the network request" , NetworkErrorException (cause)
824915 )
825916 }
826917 return AuthenticationException (
827- " Something went wrong" ,
828- Auth0Exception (" Something went wrong" , cause)
918+ " Something went wrong" , Auth0Exception (" Something went wrong" , cause)
829919 )
830920 }
831921 }
0 commit comments