Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,31 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
return this
}

/**
* Creates a new [MfaApiClient] to handle a multi-factor authentication transaction.
*
* Example usage:
* ```
* try {
* val credentials = authClient.login("user@example.com", "password").await()
* } catch (error: AuthenticationException) {
* if (error.isMultifactorRequired) {
* val mfaToken = error.mfaToken
* if (mfaToken != null) {
* val mfaClient = authClient.mfa(mfaToken)
* // Use mfaClient to handle MFA flow
* }
* }
* }
* ```
*
* @param mfaToken The token received in the 'mfa_required' error from a login attempt.
* @return A new [MfaApiClient] instance configured for the transaction.
*/
public fun mfa(mfaToken: String): MfaApiClient {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename it to mfaClient

return MfaApiClient(this.auth0, mfaToken)
}

/**
* Log in a user with email/username and password for a connection/realm.
* It will use the password-realm grant type for the `/oauth/token` endpoint
Expand Down Expand Up @@ -1081,7 +1106,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
return factory.get(url.toString(), userProfileAdapter, dPoP)
}

private companion object {
internal companion object {
private const val SMS_CONNECTION = "sms"
private const val EMAIL_CONNECTION = "email"
private const val USERNAME_KEY = "username"
Expand Down Expand Up @@ -1122,7 +1147,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
private const val WELL_KNOWN_PATH = ".well-known"
private const val JWKS_FILE_PATH = "jwks.json"
private const val TAG = "AuthenticationAPIClient"
private fun createErrorAdapter(): ErrorAdapter<AuthenticationException> {
internal fun createErrorAdapter(): ErrorAdapter<AuthenticationException> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why make them internal ?

Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The companion object visibility is changed from private to internal, which expands the API surface. Unless there's a specific requirement for internal access to createErrorAdapter, this change increases coupling between internal components and may make future refactoring more difficult.

Suggested change
internal fun createErrorAdapter(): ErrorAdapter<AuthenticationException> {
private fun createErrorAdapter(): ErrorAdapter<AuthenticationException> {

Copilot uses AI. Check for mistakes.
val mapAdapter = forMap(GsonProvider.gson)
return object : ErrorAdapter<AuthenticationException> {
override fun fromRawResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import android.util.Log
import com.auth0.android.Auth0Exception
import com.auth0.android.NetworkErrorException
import com.auth0.android.provider.TokenValidationException
import com.auth0.android.request.internal.GsonProvider
import com.auth0.android.result.MfaRequirements

public class AuthenticationException : Auth0Exception {
private var code: String? = null
Expand Down Expand Up @@ -147,6 +149,26 @@ public class AuthenticationException : Auth0Exception {
public val isMultifactorEnrollRequired: Boolean
get() = "a0.mfa_registration_required" == code || "unsupported_challenge_type" == code

/**
* The MFA token returned when multi-factor authentication is required.
* This token should be used to create an [MfaApiClient] to continue the MFA flow.
*/
public val mfaToken: String?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep this as part of the MfaRequirements error body and not separate.

get() = getValue("mfa_token") as? String

/**
* The MFA requirements returned when multi-factor authentication is required.
* Contains information about the required challenge types.
*/
public val mfaRequirements: MfaRequirements?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope the name is consistent with Auth0.Swift implementation

get() = (getValue("mfa_requirements") as? Map<*, *>)?.let {
@Suppress("UNCHECKED_CAST")
GsonProvider.gson.fromJson(
GsonProvider.gson.toJson(it),
MfaRequirements::class.java
)
}
Comment on lines +163 to +170
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AuthenticationException.mfaRequirements property performs JSON serialization/deserialization on every access, which is inefficient if the property is accessed multiple times. Consider caching the deserialized MfaRequirements object in a lazy-initialized backing field to avoid repeated conversions.

Copilot uses AI. Check for mistakes.

/// When Bot Protection flags the request as suspicious
public val isVerificationRequired: Boolean
get() = "requires_verification" == code
Expand Down
Loading