Skip to content

Commit f94c6bc

Browse files
authored
Expanding Flexible Factors Grant Android Support (auth0#896)
2 parents 6aa2f7e + 416e1c2 commit f94c6bc

File tree

16 files changed

+3518
-5
lines changed

16 files changed

+3518
-5
lines changed

EXAMPLES.md

Lines changed: 877 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.annotation.VisibleForTesting
55
import com.auth0.android.Auth0
66
import com.auth0.android.Auth0Exception
77
import com.auth0.android.NetworkErrorException
8+
import com.auth0.android.authentication.mfa.MfaApiClient
89
import com.auth0.android.dpop.DPoP
910
import com.auth0.android.dpop.DPoPException
1011
import com.auth0.android.dpop.SenderConstraining
@@ -84,6 +85,31 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
8485
return this
8586
}
8687

88+
/**
89+
* Creates a new [MfaApiClient] to handle a multi-factor authentication transaction.
90+
*
91+
* Example usage:
92+
* ```
93+
* try {
94+
* val credentials = authClient.login("user@example.com", "password").await()
95+
* } catch (error: AuthenticationException) {
96+
* if (error.isMultifactorRequired) {
97+
* val mfaToken = error.mfaRequiredErrorPayload?.mfaToken
98+
* if (mfaToken != null) {
99+
* val mfaClient = authClient.mfaClient(mfaToken)
100+
* // Use mfaClient to handle MFA flow
101+
* }
102+
* }
103+
* }
104+
* ```
105+
*
106+
* @param mfaToken The token received in the 'mfa_required' error from a login attempt.
107+
* @return A new [MfaApiClient] instance configured for the transaction.
108+
*/
109+
public fun mfaClient(mfaToken: String): MfaApiClient {
110+
return MfaApiClient(this.auth0, mfaToken)
111+
}
112+
87113
/**
88114
* Log in a user with email/username and password for a connection/realm.
89115
* It will use the password-realm grant type for the `/oauth/token` endpoint
@@ -1081,7 +1107,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
10811107
return factory.get(url.toString(), userProfileAdapter, dPoP)
10821108
}
10831109

1084-
private companion object {
1110+
internal companion object {
10851111
private const val SMS_CONNECTION = "sms"
10861112
private const val EMAIL_CONNECTION = "email"
10871113
private const val USERNAME_KEY = "username"

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import android.util.Log
55
import com.auth0.android.Auth0Exception
66
import com.auth0.android.NetworkErrorException
77
import com.auth0.android.provider.TokenValidationException
8+
import com.auth0.android.result.MfaFactor
9+
import com.auth0.android.result.MfaRequiredErrorPayload
10+
import com.auth0.android.result.MfaRequirements
811

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

153+
/**
154+
* Extracts the MFA required error payload when multifactor authentication is required.
155+
*
156+
* This property decodes the error values into a structured [MfaRequiredErrorPayload] object
157+
* containing the MFA token and enrollment requirements.
158+
*
159+
* ## Usage
160+
*
161+
* ```kotlin
162+
* if (error.isMultifactorRequired) {
163+
* val mfaPayload = error.mfaRequiredErrorPayload
164+
* val mfaToken = mfaPayload?.mfaToken
165+
* val enrollmentTypes = mfaPayload?.mfaRequirements?.enroll
166+
* }
167+
* ```
168+
*
169+
* @see isMultifactorRequired
170+
* @see MfaRequiredErrorPayload
171+
*/
172+
public val mfaRequiredErrorPayload: MfaRequiredErrorPayload?
173+
get() {
174+
val mfaToken = getValue("mfa_token") as? String ?: return null
175+
val errorCode = getCode()
176+
val errorDesc = getDescription()
177+
val requirements = getValue("mfa_requirements") as? Map<*, *>
178+
179+
@Suppress("UNCHECKED_CAST")
180+
val challengeList = (requirements?.get("challenge") as? List<Map<String, Any>>)?.map {
181+
MfaFactor(it["type"] as? String ?: "")
182+
}
183+
184+
@Suppress("UNCHECKED_CAST")
185+
val enrollList = (requirements?.get("enroll") as? List<Map<String, Any>>)?.map {
186+
MfaFactor(it["type"] as? String ?: "")
187+
}
188+
189+
return MfaRequiredErrorPayload(
190+
error = errorCode,
191+
errorDescription = errorDesc,
192+
mfaToken = mfaToken,
193+
mfaRequirements = if (challengeList != null || enrollList != null) {
194+
MfaRequirements(enroll = enrollList, challenge = challengeList)
195+
} else null
196+
)
197+
}
198+
150199
/// When Bot Protection flags the request as suspicious
151200
public val isVerificationRequired: Boolean
152201
get() = "requires_verification" == code

0 commit comments

Comments
 (0)