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
514 changes: 514 additions & 0 deletions EXAMPLES.md

Large diffs are not rendered by default.

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.mfaRequiredErrorPayload?.mfaToken
* if (mfaToken != null) {
* val mfaClient = authClient.mfaClient(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 mfaClient(mfaToken: String): MfaApiClient {
return MfaApiClient(this.auth0, mfaToken)
}
Comment on lines +108 to +110
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

PR description lists the new API as AuthenticationAPIClient.mfa(mfaToken), but the implementation adds mfaClient(mfaToken). Please align the public API naming with the PR description (either rename the method or update the PR description/docs to match).

Copilot uses AI. Check for mistakes.

/**
* 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import android.util.Log
import com.auth0.android.Auth0Exception
import com.auth0.android.NetworkErrorException
import com.auth0.android.provider.TokenValidationException
import com.auth0.android.result.MfaFactor
import com.auth0.android.result.MfaRequiredErrorPayload
import com.auth0.android.result.MfaRequirements

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

/**
* Extracts the MFA required error payload when multifactor authentication is required.
*
* This property decodes the error values into a structured [MfaRequiredErrorPayload] object
* containing the MFA token and enrollment requirements.
*
* ## Usage
*
* ```kotlin
* if (error.isMultifactorRequired) {
* val mfaPayload = error.mfaRequiredErrorPayload
* val mfaToken = mfaPayload?.mfaToken
* val enrollmentTypes = mfaPayload?.mfaRequirements?.enroll
* }
* ```
*
* @see isMultifactorRequired
* @see MfaRequiredErrorPayload
*/
public val mfaRequiredErrorPayload: MfaRequiredErrorPayload?
get() {
val mfaToken = getValue("mfa_token") as? String ?: return null
val errorCode = getCode()
val errorDesc = getDescription()
val requirements = getValue("mfa_requirements") as? Map<*, *>

@Suppress("UNCHECKED_CAST")
val challengeList = (requirements?.get("challenge") as? List<Map<String, Any>>)?.map {
MfaFactor(it["type"] as? String ?: "")
}

@Suppress("UNCHECKED_CAST")
val enrollList = (requirements?.get("enroll") as? List<Map<String, Any>>)?.map {
MfaFactor(it["type"] as? String ?: "")
}

return MfaRequiredErrorPayload(
error = errorCode,
errorDescription = errorDesc,
mfaToken = mfaToken,
mfaRequirements = if (challengeList != null || enrollList != null) {
MfaRequirements(enroll = enrollList, challenge = challengeList)
} else null
)
}

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