Skip to main content
Multi Factor Authentication

Implement WebAuthn as a secondary factor

Overview

This guide shows how to implement an MFA policy that requires all users to use WebAuthn before they get access to your application.

Before you start

The tutorial assumes that the first factor is email password or social login, but the same set of steps are applicable for other first factor types.

This feature is only available to paid users.

Steps

What is your setup type?

1. Configure the backend

To start with, we configure the backend in the following way:

import supertokens from "supertokens-node";
import ThirdParty from "supertokens-node/recipe/thirdparty"
import EmailPassword from "supertokens-node/recipe/emailpassword"
import MultiFactorAuth from "supertokens-node/recipe/multifactorauth"
import webauthn from "supertokens-node/recipe/webauthn"
import Session from "supertokens-node/recipe/session"

supertokens.init({
supertokens: {
connectionURI: "..."
},
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "..."
},
recipeList: [
Session.init(),
ThirdParty.init({
//...
}),
EmailPassword.init({
//...
}),
webauthn.init(),
MultiFactorAuth.init({
firstFactors: [
MultiFactorAuth.FactorIds.EMAILPASSWORD,
MultiFactorAuth.FactorIds.THIRDPARTY
],
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
getMFARequirementsForAuth: async function (input) {
// Change this implementation if you want to require webauthn only for specific users
return [MultiFactorAuth.FactorIds.WEBAUTHN]
}
}
}
}
})
]
})

The MFA recipe override is required to indicate that webauthn must be completed before the user can access the app.

Once the user finishes the first factor (for example, with emailpassword), their session access token payload will look like this:

{
"st-mfa": {
"c": {
"emailpassword": 1702877939,
},
"v": false
}
}

The v being false indicates that there are still factors that are pending. After the user has finished webauthn, the payload will look like:

{
"st-mfa": {
"c": {
"emailpassword": 1702877939,
"webauthn": 1702877999
},
"v": true
}
}

Indicating that the user has finished all required factors, and should be allowed to access the app.

2. Configure the frontend

What type of UI are you using?

We start by modifying the init function call on the frontend like so:

import supertokens from "supertokens-auth-react"
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth"
import webauthn from "supertokens-auth-react/recipe/webauthn"

supertokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
},
recipeList: [
// other recipes..
webauthn.init(),
MultiFactorAuth.init({
firstFactors: [
MultiFactorAuth.FactorIds.EMAILPASSWORD,
MultiFactorAuth.FactorIds.THIRDPARTY
]
})
]
})

On the frontend, the MultiFactorAuth recipe intialization only requires the first factors to be configured. The secondary factors will be determined based on a requiest to the backend.

Add the WebAuthn pre-built UI to render the SuperTokens component:

Do you use react-router-dom?

That's it! 🎉

Based on this configuration, users first access the authentication form which shows the emailpassword and thirdparty options. After first factor completion, they access the WebAuthn form to finalize the authentication attempt.