Using the TOTP MFA method in Azure AD B2C with an authenticator application
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:
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:
Enter the six-digit code from any of your registered authenticator apps.
The JWT shows:
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 😄
All good!