Skip to content

feat: add My Account API support for managing MFA authentication method#835

Open
utkrishtsahu wants to merge 3 commits into
mainfrom
feature/my-account-api
Open

feat: add My Account API support for managing MFA authentication method#835
utkrishtsahu wants to merge 3 commits into
mainfrom
feature/my-account-api

Conversation

@utkrishtsahu
Copy link
Copy Markdown
Contributor

  • All new/changed/fixed functionality is covered by tests (or N/A)
  • I have added documentation for all new/changed functionality (or N/A)

📋 Changes

Adds support for the Auth0 My Account API, enabling end-users to self-manage their MFA authentication methods without requiring Management API tokens.

New public API:

  • Auth0.myAccount(accessToken:) — factory method returning a MyAccountApi instance
  • MyAccountApi class with the following methods:
  • getAuthenticationMethods() — list all enrolled authentication methods
  • getAuthenticationMethod(id:) — get a specific method by ID
  • deleteAuthenticationMethod(id:) — delete a method by ID
  • getFactors() — list available factors on the tenant
  • enrollPhone(phoneNumber:, type:) — enroll phone (SMS/voice)
  • enrollEmail(email:) — enroll email OTP
  • enrollTotp() — enroll TOTP (authenticator app)
  • enrollPush() — enroll push notifications
  • enrollRecoveryCode() — enroll recovery codes
  • verifyOtp(id:, authSession:, otp:) — confirm enrollment with OTP

New types added:

  • MyAccountApi — main API class
  • AuthenticationMethod — represents an enrolled MFA method
  • EnrollmentChallenge — returned by enrollment methods (contains id, authSession, and type-specific fields like totpSecret, totpUri, barcodeUri, recoveryCode)
  • Factor — represents an available factor (name, enabled)
  • PhoneType — enum (sms, voice)
  • MyAccountException — domain exception with statusCode, isNetworkError, isRetryable

Platform implementation:

  • Android: Auth0FlutterMyAccountMethodCallHandler + 10 individual request handlers via MyAccountAPIClient
  • iOS/macOS: MyAccountHandler + 10 individual method handlers via Auth0.swift MyAccount client
  • Method channel: auth0.com/auth0_flutter/my_account

Requires access tokens with audience https://{domain}/me/ and appropriate me scopes (read:me:authentication_methods, create:me:authentication_methods, delete:me:authentication_methods, read:me:factors).

📎 References

SDK-8730

🎯 Testing

Unit tests:

  • Android: 11 new test files covering all request handlers + method call handler routing (247 tests total, all passing)
  • iOS: 39 new tests covering all method handlers via spy/mock objects (250 tests total, all passing)

Manual testing:

  • Tested on iOS Simulator (iPhone 16, iOS 18.4) and Android emulator
  • Verified full enrollment flow: login with My Account audience → enroll phone (SMS) → verify OTP → list methods → delete method
  • Verified TOTP enrollment returns correct totpSecret and totpUri for authenticator app integration

Not tested:

  • Web platform (not supported )

private const val MY_ACCOUNT_DELETE_AUTH_METHOD_METHOD =
"myAccount#deleteAuthenticationMethod"

class DeleteAuthenticationMethodRequestHandler :
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is final class required here?

override fun onSuccess(
res: AuthenticationMethod
) {
result.success(null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is flutter compatible with suspend functions?

result.success(res.map {
mapOf(
"name" to it.type,
"enabled" to true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Factor has type and usage. Dont we want to pass usage as well to dart?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agree, usage array should be returned

result.success(res.map {
mapOf(
"name" to it.type,
"enabled" to true
Copy link
Copy Markdown
Contributor

@sanchitmehtagit sanchitmehtagit May 21, 2026

Choose a reason for hiding this comment

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

"enabled" : true should this be derived from response.?

override fun onSuccess(res: List<Factor>) {
result.success(res.map {
mapOf(
"name" to it.type,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Lets keep it as type only. Its not actually a name thats returned.

result.success(res.map {
mapOf(
"name" to it.type,
"enabled" to true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What exactly does this enabled represent. The API doesn't return an enabled property

request: MethodCallRequest,
result: MethodChannel.Result
) {
client.getAuthenticationMethods()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This API now supports a type parameter as input. Android PR is already up.
auth0/Auth0.Android#974
Both platforms will have this out by next week. Update accordingly

override fun onSuccess(
res: AuthenticationMethod
) {
result.success(null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is the success returning null here. Shouldn't the details from AuthenticationMethod type be returned here ?

val handler = myAccountRequestHandlers.find { it.method == call.method }
if (handler != null) {
val accessToken = request.data["accessToken"] as? String ?: ""
val client = MyAccountAPIClient(request.account, accessToken)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MyAccountAPI's support DPoP too. Ensure the client created can support DPoP also.
auth0/Auth0.Android#974
This PR adds the support for the same in Android

put("_statusCode", exception.statusCode)
put("_errorFlags", mapOf(
"isNetworkError" to exception.isNetworkError,
))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The MyAccountException returns more info which actually represents what went wrong like title, detail etc. These help the customer know what went wrong and also help us debug when an issue arises. Currently the above map captures only the status code, which in itself is not helpful. Add the other properties too

return buildMap {
put("id", id)
put("type", type)
put("created_at", createdAt)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

there is a usage property also

is PushNotificationAuthenticationMethod -> {
put("name", method.name)
}
else -> {}
Copy link
Copy Markdown
Contributor

@pmathew92 pmathew92 May 22, 2026

Choose a reason for hiding this comment

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

Passkey , Recovery code and Password are missing

put("name", method.name)
}
is PushNotificationAuthenticationMethod -> {
put("name", method.name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The confirmed property is also missing which is important to know whether a authentication method is verfied or nor

put("recovery_code", challenge.recoveryCode)
}
is MfaEnrollmentChallenge -> {}
else -> {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oob enrollment challenge is missing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants