Using the TOTP MFA method in Azure AD B2C with an authenticator application

Rory Braybrook
The new control plane
4 min readAug 26, 2024
Image of SafeID dongle showing 6 digit code
Jeffery Birks, CC BY-SA 4.0, via Wikimedia

The 6-digit number generated by authenticator apps, such as Microsoft Authenticator or Google Authenticator, is based on the Time-based One-Time Password (TOTP) algorithm (RFC 6238).

It is time-based, and the code is generated based on the current time and changes every 30 seconds.

Each OTP code is unique and can only be used once.

When you set up an authenticator app, a shared secret (a base32-encoded string) is established between the app and the service. The app uses the current time (in UTC) to generate the code. The shared secret and the current time are combined using the HMAC-SHA1 algorithm to produce a hash. The hash is truncated to create a 6-digit code.

This process ensures that the code is synchronized between the authenticator app and the service, providing a secure method for two-factor authentication.

The sample is here.

This sample works, but there is a problem. It tracks the number of registered TOTP authenticators using “numberOfAvailableDevices.”

If this = 0, then onboard an authenticator.

If this > 0, then verify the code.

This means it is not possible to add more than one device.

I modified the policy so that it only does onboarding but does it multiple times.

As usual, the gist is here.

You can use any authenticator app for this if it can scan a QR code.

I used:

  • Microsoft authenticator
  • Google authenticator
  • Okta Verify authenticator
  • Duo Mobile authenticator

The secrets are all stored on the back end.

When you want to verify via MFA, which app can you use?

The answer is any of them. B2C checks all the secrets to see if one matches!

When you run the policy, you see this:

Image showing QR code to scan

When you hover over the QR code, you see:

otpauth://totp/tenant:totpuser@company.co.nz?secret=123456&issuer=tenant

You then scan the code with the authenticator app, add the user to the app., and verify with a 6-digit code.

When you authenticate with the original sample, after typing in the login and password, you see:

Image asking the user to enter the verification code from the authenticator app.

Enter the six-digit code from any of your registered authenticator apps.

The JWT shows:

Image showing number of devives = 4

Note: I have added these claims to the RP:

<OutputClaim ClaimTypeReferenceId="totpIdentifier" />
<OutputClaim ClaimTypeReferenceId="numberOfAvailableDevices" />

You see the number of devices = 4.

There are some Graph API methods that work with this.

The options available are:

  • List
  • Get
  • Delete

Using Graph Explorer, the List command is:

GET
https://graph.microsoft.com/v1.0/users/771...8f2/authentication/softwareOathMethods

where “771…8f2” is the user’s objectId.

This results in:

{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users
('771...8f2')/authentication/softwareOathMethods",
"@microsoft.graph.tips": "Use $select to choose only the properties your
app needs, as this can lead to performance improvements. For example:
GET users('<guid>')/authentication/softwareOathMethods?$select=secretKey",
"value": [
{
"id": "121...2cf",
"secretKey": null
},
{
"id": "8b7...0c7",
"secretKey": null
},
{
"id": "53c...2bf",
"secretKey": null
},
{
"id": "b78...9d3",
"secretKey": null
}
]
}

For the record, adding “?$select=secretKey” produces the same results 😃.

Note that there are four elements in the array that match the number of authenticator methods.

The Get command is:

GET
https://graph.microsoft.com/v1.0/users/771...8f2/authentication/softwareOathMethods/8b7...0c7

where the second ID is the ID of one of the methods above.

This results in:

{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users
('771...8f2')/authentication/softwareOathMethods
/$entity",
"@microsoft.graph.tips": "Use $select to choose only the properties your
app needs, as this can lead to performance improvements. For example: GET
users('<guid>')/authentication/softwareOathMethods('<guid>')?$select=secretKey",
"id": "8b7...0c7",
"secretKey": null
}

which gives you back what you already know? You input the ID and get the same ID back?

The Delete command is:

DELETE
https://graph.microsoft.com/v1.0/users/771...8f2/authentication/softwareOathMethods/8b7...0c7

This results in:

No Content - 204

Now, when we run the List command again, the result is:

"value": [
{
"id": "b78...9d3",
"secretKey": null
},
{
"id": "53c...2bf",
"secretKey": null
},
{
"id": "121...2cf",
"secretKey": null
}
]

and we see the “8b7…0c7” method has been deleted.

I cannot find a way to match the ID to the actual method, e.g. which ID maps to Microsoft Authenticator?

Fun fact: when I googled Wikimedia to find an image of TOTP to place at the top of the post, it came up with this 😄

Totp_logo_1986.jpg from Wikimedia

All good!

--

--

The new control plane
The new control plane

Published in The new control plane

“Identity is the new control plane”. Articles around Microsoft Identity, Auth0 and identityserver. Click the “Archive” link at the bottom for more posts.

Rory Braybrook
Rory Braybrook

Written by Rory Braybrook

NZ Microsoft Identity dude and MVP. Azure AD/B2C/ADFS/Auth0/identityserver. StackOverflow: https://bit.ly/2XU4yvJ Presentations: http://bit.ly/334ZPt5

No responses yet