Skip to content

Commit 2cf87a2

Browse files
committed
docs: Align MFA examples
1 parent 0ec834b commit 2cf87a2

File tree

4 files changed

+62
-121
lines changed

4 files changed

+62
-121
lines changed

EXAMPLES.md

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -445,9 +445,10 @@ When MFA is required during authentication, the error response contains a struct
445445
| `mfaRequirements.challenge` | `List<MfaFactor>?` | Factor types available for challenge. Present when the user **has already enrolled** MFA factors. |
446446

447447
**Enroll vs Challenge Flows:**
448-
- **Enroll flow**: When `mfaRequirements.enroll` is present (and `challenge` is null or empty), the user needs to enroll a new MFA factor before they can authenticate. Use `mfaClient.enroll()` to register a new authenticator.
449-
- **Challenge flow**: When `mfaRequirements.challenge` is present, the user has already enrolled MFA factors. Use `mfaClient.getAuthenticators()` to list their enrolled authenticators, then `mfaClient.challenge()` to initiate verification.
450-
- **Both present**: In some configurations, both `enroll` and `challenge` may be present, allowing the user to either verify with an existing factor or enroll a new one.
448+
- **Enroll flow**: When `mfaRequirements.enroll` is present and not empty, the user needs to enroll a new MFA factor before they can authenticate. Use `mfaClient.enroll()` to register a new authenticator.
449+
- **Challenge flow**: When `mfaRequirements.challenge` is present and not empty, the user has already enrolled MFA factors. Use `mfaClient.getAuthenticators()` to list their enrolled authenticators, then `mfaClient.challenge()` to initiate verification.
450+
451+
> **Note**: Check both `enroll` and `challenge` independently. While typically only one will be present, your code should handle both scenarios defensively.
451452
452453
#### Handling MFA Required Errors
453454

@@ -465,9 +466,21 @@ authentication
465466
val mfaToken = mfaPayload?.mfaToken
466467
val requirements = mfaPayload?.mfaRequirements
467468

468-
// Check what actions are available (these are factor types, not authenticators)
469-
val canChallenge = requirements?.challenge // List of factor types the user can challenge
470-
val canEnroll = requirements?.enroll // List of factor types the user can enroll
469+
// Check if enrollment is required (user has not enrolled MFA yet)
470+
requirements?.enroll?.let { enrollTypes ->
471+
println("User needs to enroll MFA")
472+
println("Available enrollment types: ${enrollTypes.map { it.type }}")
473+
// Example output: ["otp", "sms", "push-notification"]
474+
// Proceed with MFA enrollment using one of these types
475+
}
476+
477+
// Check if challenge is available (user already enrolled)
478+
requirements?.challenge?.let { challengeTypes ->
479+
println("User has enrolled MFA factors")
480+
println("Available challenge types: ${challengeTypes.map { it.type }}")
481+
// Example output: ["otp", "sms"]
482+
// Get authenticators and challenge one of them
483+
}
471484

472485
// Proceed with MFA flow using mfaToken
473486
}
@@ -493,7 +506,23 @@ try {
493506
if (e.isMultifactorRequired) {
494507
val mfaPayload = e.mfaRequiredErrorPayload
495508
val mfaToken = mfaPayload?.mfaToken
496-
// Proceed with MFA flow
509+
val requirements = mfaPayload?.mfaRequirements
510+
511+
// Check if enrollment is required
512+
requirements?.enroll?.let { enrollTypes ->
513+
println("User needs to enroll MFA")
514+
println("Available enrollment types: ${enrollTypes.map { it.type }}")
515+
// Example output: ["otp", "sms", "push-notification"]
516+
}
517+
518+
// Check if challenge is available
519+
requirements?.challenge?.let { challengeTypes ->
520+
println("User has enrolled MFA factors")
521+
println("Available challenge types: ${challengeTypes.map { it.type }}")
522+
// Example output: ["otp", "sms"]
523+
}
524+
525+
// Proceed with MFA flow using mfaToken
497526
}
498527
}
499528
```

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,8 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
8484
GsonProvider.gson
8585
)
8686

87-
private val clientId: String
88-
get() = auth0.clientId
89-
private val baseURL: String
90-
get() = auth0.getDomainUrl()
87+
private val clientId: String = auth0.clientId
88+
private val baseURL: String = auth0.getDomainUrl()
9189

9290
/**
9391
* Retrieves the list of available authenticators for the user, filtered by the specified factor types.

auth0/src/test/java/com/auth0/android/authentication/MfaExceptionTest.kt

Lines changed: 0 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package com.auth0.android.authentication
22

33
import com.auth0.android.authentication.MfaException.*
4-
import com.auth0.android.authentication.storage.CredentialsManagerException
5-
import com.auth0.android.result.MfaFactor
6-
import com.auth0.android.result.MfaRequiredErrorPayload
7-
import com.auth0.android.result.MfaRequirements
84
import org.hamcrest.MatcherAssert.assertThat
95
import org.hamcrest.Matchers.*
106
import org.junit.Test
@@ -303,110 +299,4 @@ public class MfaExceptionTest {
303299
assertThat(verifyException.message, containsString("custom_error_code"))
304300
}
305301

306-
// ========== CredentialsManagerException MFA Tests ==========
307-
308-
@Test
309-
public fun shouldCredentialsManagerExceptionHaveNullMfaPayloadByDefault(): Unit {
310-
val exception = CredentialsManagerException.RENEW_FAILED
311-
312-
assertThat(exception.mfaRequiredErrorPayload, `is`(nullValue()))
313-
assertThat(exception.mfaToken, `is`(nullValue()))
314-
}
315-
316-
@Test
317-
public fun shouldCredentialsManagerExceptionMfaRequiredHaveCorrectMessage(): Unit {
318-
val exception = CredentialsManagerException.MFA_REQUIRED
319-
320-
assertThat(exception.message, containsString("Multi-factor authentication is required"))
321-
}
322-
323-
@Test
324-
public fun shouldCredentialsManagerExceptionMfaTokenReturnCorrectValue(): Unit {
325-
// Create an MFA payload with a token
326-
val mfaPayload = MfaRequiredErrorPayload(
327-
error = "mfa_required",
328-
errorDescription = "Multifactor authentication required",
329-
mfaToken = "test_mfa_token_123",
330-
mfaRequirements = null
331-
)
332-
333-
// Use reflection to create exception with payload since constructor is internal
334-
val exceptionClass = CredentialsManagerException::class.java
335-
val constructor = exceptionClass.getDeclaredConstructor(
336-
CredentialsManagerException.Code::class.java,
337-
String::class.java,
338-
Throwable::class.java,
339-
MfaRequiredErrorPayload::class.java
340-
)
341-
constructor.isAccessible = true
342-
343-
val codeClass = Class.forName("com.auth0.android.authentication.storage.CredentialsManagerException\$Code")
344-
val mfaRequiredCode = codeClass.getDeclaredField("MFA_REQUIRED").get(null)
345-
346-
val exception = constructor.newInstance(
347-
mfaRequiredCode,
348-
"MFA required",
349-
null,
350-
mfaPayload
351-
) as CredentialsManagerException
352-
353-
assertThat(exception.mfaRequiredErrorPayload, `is`(notNullValue()))
354-
assertThat(exception.mfaToken, `is`("test_mfa_token_123"))
355-
assertThat(exception.mfaRequiredErrorPayload?.mfaToken, `is`("test_mfa_token_123"))
356-
}
357-
358-
@Test
359-
public fun shouldCredentialsManagerExceptionMfaPayloadContainRequirements(): Unit {
360-
// Create MFA requirements with challenge
361-
val challengeFactors = listOf(MfaFactor(type = "otp"), MfaFactor(type = "sms"))
362-
val requirements = MfaRequirements(challenge = challengeFactors, enroll = null)
363-
val mfaPayload = MfaRequiredErrorPayload(
364-
error = "mfa_required",
365-
errorDescription = "Multifactor authentication required",
366-
mfaToken = "token_with_requirements",
367-
mfaRequirements = requirements
368-
)
369-
370-
// Use reflection to create exception with payload
371-
val exceptionClass = CredentialsManagerException::class.java
372-
val constructor = exceptionClass.getDeclaredConstructor(
373-
CredentialsManagerException.Code::class.java,
374-
String::class.java,
375-
Throwable::class.java,
376-
MfaRequiredErrorPayload::class.java
377-
)
378-
constructor.isAccessible = true
379-
380-
val codeClass = Class.forName("com.auth0.android.authentication.storage.CredentialsManagerException\$Code")
381-
val mfaRequiredCode = codeClass.getDeclaredField("MFA_REQUIRED").get(null)
382-
383-
val exception = constructor.newInstance(
384-
mfaRequiredCode,
385-
"MFA required",
386-
null,
387-
mfaPayload
388-
) as CredentialsManagerException
389-
390-
assertThat(exception.mfaRequiredErrorPayload, `is`(notNullValue()))
391-
assertThat(exception.mfaRequiredErrorPayload?.mfaRequirements, `is`(notNullValue()))
392-
assertThat(exception.mfaRequiredErrorPayload?.mfaRequirements?.challenge?.map { it.type }, `is`(listOf("otp", "sms")))
393-
}
394-
395-
@Test
396-
public fun shouldCredentialsManagerExceptionEqualityIgnoreMfaPayload(): Unit {
397-
// Two MFA_REQUIRED exceptions should be equal regardless of payload
398-
val exception1 = CredentialsManagerException.MFA_REQUIRED
399-
val exception2 = CredentialsManagerException.MFA_REQUIRED
400-
401-
assertThat(exception1, `is`(exception2))
402-
assertThat(exception1.hashCode(), `is`(exception2.hashCode()))
403-
}
404-
405-
@Test
406-
public fun shouldCredentialsManagerExceptionStaticInstancesBeDistinct(): Unit {
407-
assertThat(CredentialsManagerException.MFA_REQUIRED, `is`(not(CredentialsManagerException.RENEW_FAILED)))
408-
assertThat(CredentialsManagerException.MFA_REQUIRED, `is`(not(CredentialsManagerException.NO_CREDENTIALS)))
409-
assertThat(CredentialsManagerException.MFA_REQUIRED, `is`(not(CredentialsManagerException.API_ERROR)))
410-
}
411-
412302
}

auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1948,6 +1948,30 @@ public class CredentialsManagerTest {
19481948
MatcherAssert.assertThat(retrievedCredentials.scope, Is.`is`("scope"))
19491949
}
19501950

1951+
// ========== CredentialsManagerException MFA Tests ==========
1952+
1953+
@Test
1954+
public fun shouldCredentialsManagerExceptionHaveNullMfaPayloadByDefault() {
1955+
val exception = CredentialsManagerException.RENEW_FAILED
1956+
1957+
MatcherAssert.assertThat(exception.mfaRequiredErrorPayload, Is.`is`(Matchers.nullValue()))
1958+
MatcherAssert.assertThat(exception.mfaToken, Is.`is`(Matchers.nullValue()))
1959+
}
1960+
1961+
@Test
1962+
public fun shouldCredentialsManagerExceptionMfaRequiredHaveCorrectMessage() {
1963+
val exception = CredentialsManagerException.MFA_REQUIRED
1964+
1965+
MatcherAssert.assertThat(exception.message, Matchers.containsString("Multi-factor authentication is required"))
1966+
}
1967+
1968+
@Test
1969+
public fun shouldCredentialsManagerExceptionStaticInstancesBeDistinct() {
1970+
MatcherAssert.assertThat(CredentialsManagerException.MFA_REQUIRED, Is.`is`(Matchers.not(CredentialsManagerException.RENEW_FAILED)))
1971+
MatcherAssert.assertThat(CredentialsManagerException.MFA_REQUIRED, Is.`is`(Matchers.not(CredentialsManagerException.NO_CREDENTIALS)))
1972+
MatcherAssert.assertThat(CredentialsManagerException.MFA_REQUIRED, Is.`is`(Matchers.not(CredentialsManagerException.API_ERROR)))
1973+
}
1974+
19511975
private fun prepareJwtDecoderMock(expiresAt: Date?) {
19521976
val jwtMock = mock<Jwt>()
19531977
Mockito.`when`(jwtMock.expiresAt).thenReturn(expiresAt)

0 commit comments

Comments
 (0)