Implement recovery codes
Overview
Backup codes is one of the ways in which end users can recover their account in case they lose their second factor device. At the moment, SuperTokens does not have an in-built implementation for backup codes, however, you can customize the SDKs to add it.
Here is an example of how you can implement backup codes in your application if you are using the pre-built UI.
This guide shows you how to implement the following flow:
- After MFA setup users can generate backup codes.
- The backup code associates with the userID in the
UserMetadata
JSON. - When a user wants to use their backup code, the application shows them a UI to enter the code. This calls an API that verifies the backup code and adds a flag in their session, indicating that they have correctly supplied a backup code.
- After that, the flag allows users to create a new MFA device, even if they already have one registered on their account. User can then go about adding a new device and completing MFA using that.
Before you start
This feature is only available to paid users.
These instructions assume that you already have some knowledge of MFA. If you are not familiar with terms like authentication factors and challenges, please go through the MFA concepts page.
The guide below focuses on Time-based One-Time Password (TOTP) as a second factor, but you can implement something similar for Passwordless as well.
Steps
1. Add an API to generate backup codes on the backend
Here is an example API for how you can create a recovery code. In here, you create a secure random string and save the hashed version in the user metadata JSON. This API returns the plain text recovery code to the frontend to display to the user.
2. Allow users to generate a backup code when they finish MFA setup
What type of UI are you using?
After the user has successfully set up their second factor (during sign up or during recovery process), they navigate to a page which shows them their backup code.
The user can automatically redirect to this page (once they have completed their MFA setup) by adding a claim validator on the frontend. This enforces that the user always has a backup code associated with their account. Even if they consume the code in the future, this validator fails and redirects them to the create new backup code screen.
The idea here is to modify the access token payload on the backend to add a boolean value to it. This value is true
if the user already has a backup code with their account, else it's false
.
The frontend claim validator fails if the value is false
and redirects them to the UI for creating a backup code.
You can see this validator implementation on the frontend in the recoveryCodeExistsClaim.ts file. This validator adds to the Session.init on the frontend that it runs each time you protect a route with <SessionAuth />
.
It is also necessary to add a claim validator on the backend to add the boolean value to the access token payload. You can see the claim validator's implementation in the recoveryCodeExistsClaim.ts file. This validator appears in a few places:
- When creating a new backup code.
- When consuming an existing backup code.
- When creating a new session.
Once on the page, the UI calls the API created in the previous step to create a new recovery code for the user. Note that calling this API replaces the older recovery code, but since it's all custom, you can change the logic.
Here is an example of how to implement this page.
3. Show how to use backup codes on the MFA challenge UI
You can achieve this by creating a "Lost device?" button in the pre-built UI that asks the user to enter the Time-based One-Time Password (TOTP) challenge. Once they click on this, users redirect to a page where they can enter their backup code. After verification, they further redirect to the create a new Time-based One-Time Password (TOTP) device page.
Here is how you can override the pre-built UI to display the "Lost device?" button.
Here is an example implementation of a page which asks the user to enter their backup code. It then calls an API (see next step) to check if the code is correct or not.
4. Modify the user's session to mark that they have verified their backup code
On the backend, you set up an API that accepts the recovery code entered by the user. It checks that it matches the hashed version stored in the user's metadata JSON. If it does, it marks as "in use" in the user's session payload.
You achieve this by saving the recoverCodeHash
in the session payload, which is then checked in the next step to force enable Time-based One-Time Password (TOTP) device creation.
On the frontend, once this API returns a success, the user should navigate to the create a new Time-based One-Time Password (TOTP) device screen. You can see how this process completes in the index.tsx file.
5. Force users to setup a new device
Here are the steps:
- Force enabling Time-based One-Time Password (TOTP) device creation if the
recoveryCodeHash
is in the user's session's access token payload. - Deleting the recovery code from the user's metadata JSON once they have consumed it to create a new device.
- Redirecting the user to the page that shows their new recovery code after they have set up their new device.
You can achieve the first step by overriding this function on the backend.
By default, the assertAllowedToSetupFactorElseThrowInvalidClaimError
function throws an error if a Time-based One-Time Password (TOTP) device already exists. If the user tries to set up a new TOTP device during the sign-in process, it is for security reasons.
However, this modifies to check if the access token payload contains the recoveryCodeHash
and that it matches the one in the user metadata JSON.
If it does, you allow new device setup, since it is known that the user had previously entered their recovery code successfully.
During the Time-based One-Time Password (TOTP) device setup, users call this API from the frontend. If this API succeeds, then it means that a new TOTP device has been created for the user and they have completed the TOTP challenge for the current session.
On success, the old recovery code removes from the metadata in case the session has the recoveryCodeHash
set.
This way, the old recovery code is no longer usable.
Finally, after successfully creating a new Time-based One-Time Password (TOTP) device, the frontend should redirect the user to the page which shows the new recovery code for the user. Refer to step 2 above.