Skip to content

Commit

Permalink
changes to account linking and mfa docs
Browse files Browse the repository at this point in the history
  • Loading branch information
rishabhpoddar committed May 16, 2024
1 parent 65d47f2 commit 840feb1
Show file tree
Hide file tree
Showing 15 changed files with 345 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ To enable use of sessions for multiple API endpoints, you need to use the `sessi

:::important
- All your API endpoints must have the same top level domain. For example, they can be `{"api.example.com", "api2.example.com"}`, but they cannot be `{"api.example.com", "api.otherdomain.com"}`.
- `sessionTokenBackendDomain` in the frontend config must match the `cookieDomain` set in the backend config.
- The backend config (step 1 and 2) need to only be done if you are using cookie based auth (which is the default for web apps). If using header based auth, please skip to step 3.
:::

## Step 1) Set Cookie Domain in the Backend Config
## Step 1) Set Cookie Domain in the Backend Config (only applicable for cookie based auth)

You need to set the `cookieDomain` value to be the common top level domain. For example, if your API endpoints are `{"api.example.com", "api2.example.com", "api3.example.com"}`, the common portion of these endpoints is `".example.com"` (The dot is NOT important). So you would need to set the following:

Expand Down Expand Up @@ -112,7 +112,7 @@ Whilst the `cookieDomain` can start with a leading `.`, the value of the `apiDom
For local development, you should not set the `cookieDomain` to an IP address based domain, or `.localhost` - browsers will reject these cookies. Instead, you should [alias `localhost` to a named domain and use that](https://superuser.com/questions/152146/how-to-alias-a-hostname-on-mac-osx).
:::

## Step 2) Set Older Cookie Domain in the Backend Config
## Step 2) Set Older Cookie Domain in the Backend Config (only applicable for cookie based auth)

To avoid locking out users with existing sessions (they will get a 500 error when try to refresh their session), set `olderCookieDomain` to match your previous `cookieDomain`. If your `cookieDomain` was not set, you can use an empty string. However, if you don't have any existing sessions, you can skip this step entirely.

Expand Down Expand Up @@ -208,7 +208,7 @@ init(
The `olderCookieDomain` value should be set to prevent clients from having multiple session cookies from different domains. This can happen when cookies from a previous domain are still valid and sent with requests. For instance, if your previous `cookieDomain` was `api.example.com` and the new one is `.example.com`, both sets of cookies would be sent to the apiDomain `api.example.com`, leading to an inconsistent state. This can cause issues until the older cookies are cleared. Setting `olderCookieDomain` in the configuration ensures that the SuperTokens SDK can automatically remove these older cookies.
:::

## Step 3) Frontend config
## Step 3) Frontend config (for cookie or header based auth)

You need to set the same value for `sessionTokenBackendDomain` on the frontend. This will allow the frontend SDK to apply interception and automatic refreshing across all your API calls:

Expand Down
108 changes: 22 additions & 86 deletions v2/mfa/backend-setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ MFA requires account linking to be enabled (see [here](./important-concepts#rela
```ts
import SuperTokens, { User, RecipeUserId, } from "supertokens-node";
import { UserContext } from "supertokens-node/types";
import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword"
import Passwordless from "supertokens-node/recipe/passwordless"
import MultiFactorAuth from "supertokens-node/recipe/multifactorauth"
import totp from "supertokens-node/recipe/totp"
import AccountLinking from "supertokens-node/recipe/accountlinking"
import { AccountInfoWithRecipeId } from "supertokens-node/recipe/accountlinking/types";
import { SessionContainerInterface } from "supertokens-node/recipe/session/types";
Expand All @@ -46,16 +42,25 @@ SuperTokens.init({
// highlight-start
AccountLinking.init({
shouldDoAutomaticAccountLinking: async (newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: UserContext) => {
// This will enable first factor account linking.
// For example, if a user logs in via email password with email e1,
// and then signs out and logs in via Google with the same email,
// we will link the accounts (as long as the email password user's email is
// verified).
// It will also enable account linking for MFA purposes.
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: true
if (session === undefined) {
// we do not want to do first factor account linking by default. To enable that,
// please see the automatic account linking docs in the recipe docs for your first factor.
return {
shouldAutomaticallyLink: false
};
}
if (user === undefined || session.getUserId() === user.id) {
// if it comes here, it means that a session exists, and we are trying to link the
// newAccountInfo to the session user, which means it's an MFA flow, so we enable
// linking here.
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: false
}
}
return {
shouldAutomaticallyLink: false
};
}
}),
// highlight-end
Expand All @@ -80,7 +85,9 @@ Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-i
</TabItem>
</BackendSDKTabs>

The above snippet enables account linking for first factor login, and also for MFA purposes. However, if you want to enable account linking only for MFA purposes, you can see [this section](#enabling-account-linking-only-for-mfa-purposes).
- The above snippet enables auto account linking only during the second factor and not for the first factor login. This means that if a user has an email password account, and then they login via Google separately (with the same email), those two accounts will not be linked. However, if the second factor for logging in is email or phone OTP, then that passwordless account will be linked to the first factor login method of that session.
- Notice that we have set `shouldRequireVerification: false` for account linking. It means that the second factor can be linked to the first factor even though the first factor is not verified. If you want to do email verification of the first factor before setting up the second factor (for example if the first factor is email password, and the second is phone OTP), then you can set this boolean to `true`, and also init the email verification recipe on the frontend and backend in `REQUIRED` mode.
- If you also want to enable first factor automatic account linking, see [this link](/docs/thirdpartyemailpassword/common-customizations/account-linking/automatic-account-linking).

:::important
Account linking is a paid feature, and you need to generate a license key to enable it. Enabling the MFA feature also enables account linking automatically, so you don't need to check the account linking feature separately.
Expand Down Expand Up @@ -869,75 +876,4 @@ It's a very common use case to want to override the default behaviour of SuperTo

Now since the sign up / sign in APIs are shared for first factor and second factor login, your override will be called for both first and second factor login. So if you want to have different behaviour for first and second factor login, you can use the `input` argument to the function to determine if the user is doing first or second factor login.

The `input` argument contains the `session` object using which you can determine if the user is doing first or second factor login. If the `session` property is `undefined`, it means it's a first factor login, else it's a second factor login. In the links above, the code snippets we have check for `input.session === undefined` to determine if it's a first factor login.

## Enabling account linking only for MFA purposes

The step 1 above, enables account linking for first factor login and also for MFA purposes. However, if you want to enable account linking only for MFA purposes, you can do this in the following way:

<BackendSDKTabs>
<TabItem value="nodejs">

```ts
import SuperTokens, { User, RecipeUserId, } from "supertokens-node";
import { UserContext } from "supertokens-node/types";
import AccountLinking from "supertokens-node/recipe/accountlinking"
import { AccountInfoWithRecipeId } from "supertokens-node/recipe/accountlinking/types";
import { SessionContainerInterface } from "supertokens-node/recipe/session/types";

SuperTokens.init({
supertokens: {
connectionURI: "..."
},
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "..."
},
recipeList: [
// ...
// highlight-start
AccountLinking.init({
shouldDoAutomaticAccountLinking: async (newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: UserContext) => {
if (session === undefined) {
// we do not want to do first factor account linking by default. To enable that,
// please see the automatic account linking docs in the recipe docs for your first factor.
return {
shouldAutomaticallyLink: false
};
}
if (user === undefined || session.getUserId() === user.id) {
// if it comes here, it means that a session exists, and we are trying to link the
// newAccountInfo to the session user, which means it's an MFA flow, so we enable
// linking here.
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: true
}
}
return {
shouldAutomaticallyLink: false
};
}
}),
// highlight-end
]
})
```

</TabItem>
<TabItem value="go">

:::note
Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app.
:::

</TabItem>
<TabItem value="python">

:::note
Coming soon. In the meantime, checkout the [legacy method](./legacy-method/how-it-works) for adding MFA to your app.
:::

</TabItem>
</BackendSDKTabs>
The `input` argument contains the `session` object using which you can determine if the user is doing first or second factor login. If the `session` property is `undefined`, it means it's a first factor login, else it's a second factor login. In the links above, the code snippets we have check for `input.session === undefined` to determine if it's a first factor login.
57 changes: 38 additions & 19 deletions v2/mfa/email-sms-otp/otp-for-all-users.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,25 @@ supertokens.init({
}),
AccountLinking.init({
shouldDoAutomaticAccountLinking: async (newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: UserContext) => {
// This will enable first factor account linking.
// For example, if a user logs in via email password with email e1,
// and then signs out and logs in via Google with the same email,
// we will link the accounts (as long as the email password user's email is
// verified).
// It will also enable account linking for MFA purposes.
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: true
if (session === undefined) {
// we do not want to do first factor account linking by default. To enable that,
// please see the automatic account linking docs in the recipe docs for your first factor.
return {
shouldAutomaticallyLink: false
};
}
if (user === undefined || session.getUserId() === user.id) {
// if it comes here, it means that a session exists, and we are trying to link the
// newAccountInfo to the session user, which means it's an MFA flow, so we enable
// linking here.
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: false
}
}
return {
shouldAutomaticallyLink: false
};
}
}),
// highlight-end
Expand Down Expand Up @@ -122,7 +131,8 @@ Coming soon. In the meantime, checkout the [legacy method](../legacy-method/how-
</BackendSDKTabs>

- Notice that we have initialised the Passwordless recipe in the `recipeList`. In this example, we only want to enable email based OTP, so we set the `contactMethod` to `EMAIL` and `flowType` to `USER_INPUT_CODE` (i.e. otp). If instead, you want to use phone sms based OTP, you should set the contact method to `PHONE`. If you want to give users both the options, or for some users use email, and for others use phone, you should set `contactMethod` to `EMAIL_OR_PHONE`.
- We have also enabled the account linking feature since [it's required for MFA to work](../important-concepts#relation-of-account-linking-and-mfa). The above enables account linking for first and second factor login, but if you only want to enable it for second factor, see [this section](../backend-setup#enabling-account-linking-only-for-mfa-purposes).
- We have also enabled the account linking feature since [it's required for MFA to work](../important-concepts#relation-of-account-linking-and-mfa). The above enables account linking for second factor only, but if you also want to enable it for first factor, see [this section](/docs/thirdpartyemailpassword/common-customizations/account-linking/automatic-account-linking).
- Notice that we have set `shouldRequireVerification: false` for account linking. It means that the second factor can be linked to the first factor even though the first factor is not verified. If you want to do email verification of the first factor before setting up the second factor (for example if the first factor is email password, and the second is phone OTP), then you can set this boolean to `true`, and also init the email verification recipe on the frontend and backend in `REQUIRED` mode.
- We also override the `getMFARequirementsForAuth` function to indicate that `otp-email` must be completed before the user can access the app. Notice that we do not check for the userId there, and return `otp-email` for all users. You can also return `otp-phone` instead if you want users to complete the OTP challenge via a phone SMS. Finally, if you want to give users an option for email or phone, you can return the following array from the function:

```json
Expand Down Expand Up @@ -443,16 +453,25 @@ supertokens.init({
}),
AccountLinking.init({
shouldDoAutomaticAccountLinking: async (newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: UserContext) => {
// This will enable first factor account linking.
// For example, if a user logs in via email password with email e1,
// and then signs out and logs in via Google with the same email,
// we will link the accounts (as long as the email password user's email is
// verified).
// It will also enable account linking for MFA purposes.
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: true
if (session === undefined) {
// we do not want to do first factor account linking by default. To enable that,
// please see the automatic account linking docs in the recipe docs for your first factor.
return {
shouldAutomaticallyLink: false
};
}
if (user === undefined || session.getUserId() === user.id) {
// if it comes here, it means that a session exists, and we are trying to link the
// newAccountInfo to the session user, which means it's an MFA flow, so we enable
// linking here.
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: false
}
}
return {
shouldAutomaticallyLink: false
};
}
}),
MultiFactorAuth.init()
Expand Down
Loading

0 comments on commit 840feb1

Please sign in to comment.