Associate MFA factor from own UI: error on factor activation

I want to provide an option for users to manage their MFA enrollments from our own UI, not just during the Universal Login flow. We’re getting a lot of support tickets from users that get confused/stuck by hitting such an unexpected ‘enroll your device’ page. I feel I’m super close to getting it to work, but I’m getting stuck on the very last part of the enrollement: the confirmation of the factor.

To provide this functionality, I’m using the MFA API. I’m getting an access token with audience https://{{auth0-custom-domain}}/mfa and scopes read:authenticators remove:authenticators enroll and following the instructions on Manage Authenticator Factors using the MFA API and Enroll and Challenge OTP Authenticators.

Here are the steps that work without a hitch:

  1. I get an access token for the https://{{auth0-domain}}/mfa audience with scopes enroll read:authenticators remove:authenticators when logging in with Universal Login
  2. I make a POST request to the /mfa/associate endpoint with JSON body { "authenticator_types": ["opt"] }
  3. I get a JSON response with authenticator_type, secret and barcode_uri
  4. I enter the secret in Google Authenticator and get the OTP code

So far, so good. The MFA factor is now in the user’s account as a non activated factor. So, the final step (and the big issue) is activating it. It’s important to stress I don’t have an MFA token, as at no point have I hit an mfa_required error.

Normally, you’d make a POST request to https://{{auth0-domain}}/oauth/token like this:

curl --request POST \
  --url 'https://YOUR_DOMAIN/oauth/token' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data grant_type=http://auth0.com/oauth/grant-type/mfa-otp \
  --data 'client_id={{client_id}}' \
  --data client_secret={{client_secret}}\
  --data mfa_token={{mfa_token}}\
  --data otp={{current_otp}}

But I don’t have an MFA token. Replacing it with the access token results in an "invalid_grant" error with description "invalid audience". Now, if the access token is expired, the error changes to "mfa token expired", so it seems the access token is accepted in this location and I’m this close to clearing this hurdle.

Is there a way to activate an MFA factor without hitting an mfa_required error? Or am I completely missing the point of the MFA API? Or have I simply lost my mind (social distancing does weird things to a human being)? :sweat_smile:

Any help and insights would be very much appreciated!

Gentle ping from me :ping_pong:

Hi @thijmen96,

I apologize for the delay.

Were you able to figure this one out? I can reach out to the team if you still need assistance.

Best,
Dan

Hi @dan.woda, I’m still stuck on this :sweat_smile:

Finally got some time to go through this whole flow, and after some time with postman I have some ideas.

How is the user logging in here (I think this is the crux of the issue)? Are users not required to use MFA? They should be getting a MFA required error and a MFA token.

After this you would take the MFA token from step 1 and make the request you posted, which would return an access and id token.

This is likely an unintentional red herring. The API is probably looking at the token expiration before looking at the actual payload, and saying the MFA token is expired, when it isn’t an MFA token at all, but it is expired.

1 Like

Hi Dan, thank you so much for taking the time to look into this!

We want to use this in a Node.js application, so the user is signed in normally and we specify the MFA API endpoint with the enroll scope. There’s no MFA required error or MFA token issued at this point.

The documentation for the MFA API gave me the impression there are two ways to go through this flow:

  1. Hit an MFA required error and enroll from there for the first factor a user adds
  2. Trigger enrollment for a fully authenticated user to add additional factors

I’m working under the assumption that number 1 is only possible if the user has no factors enrolled yet, as enrolling a new factor every time MFA is required would mean you can basically skip the MFA factor… How would that work when enrolling a second factor, say a user enrolls OTP first and then SMS later?

If number 2 is not possible, I’d really appreciate a suggestion on how to allow users to set up MFA in a self service dashboard. Would we have to write a rule that triggers an MFA required error whenever the enroll scope is requested and work from there?

Thanks for taking the time to describe this further. I am going to reach out to the team and see if I can get a more specific answer for you.

I’ll update here when I hear back from them.

Hey @thijmen96
It’s admittedly confusing, but the “MFA token” can be obtained in two ways:

  • what’s returned as the mfa token along with the mfa_required error in the resource owner password grant flow
  • the access token you obtain with regular /authorize request, with an audience=https://YOUR_AUTH0_DOMAIN/mfa/ and scopes enroll read:authenticators delete:authenticators

So, essentially, the same token used for enrollment is used to confirm the authenticator.

If you are using custom domains there’s a small gotcha: you can use your custom domain for the /oauth/token for all requests, but the MFA API audience will still need to be https://YOUR_AUTH0.COM_DOMAIN/mfa/.

I just tested the flow and worked on my machine :smiley: (:tm:) If it still doesn’t work for you, show me the actual domain for your request, and the payload of the access token you are trying to use.

2 Likes

It works!!1! :partying_face:

It was the custom domain gotcha… All MFA API calls also work on custom domains, even with an API audience of {{custom_domain}}/mfa/ (which I’d naively created in my dashboard… Note to self: never try to create an Auth0 API, they’re already there), except for the /oauth/token endpoint… So that’s why I got stuck at the end.

Thank you so much for your help!

2 Likes

Thanks for the help @nicolas_sabena. Let us know if you have any other questions!

1 Like