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.
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
I make a POST request to the /mfa/associate endpoint with JSON body { "authenticator_types": ["opt"] }
I get a JSON response with authenticator_type, secret and barcode_uri
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:
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)?
Any help and insights would be very much appreciated!
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.
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:
Hit an MFA required error and enroll from there for the first factor a user adds
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?
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 () 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.
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.