Skip to content
Esc
navigateopen⌘Jpreview
Dashboard
On this page

Setting up the first factor

Set up the first authentication factor using SuperTokens with various frameworks.

1. Initialisation

Start by following the recipe guide for the first factor. In this guide, we will take the example of thirdparty and emailpassword recipes as being the first factor.

After following the backend quick setup section (or any of the framework specific integration guides), you should have all the auth APIs exposed to the frontend via the SuperTokens middleware. The supertokens.init code on the server would look like this:

import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import UserMetadata from "supertokens-node/recipe/usermetadata";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import EmailPassword from "supertokens-node/recipe/emailpassword";

supertokens.init({
  framework: "express",
  supertokens: {
    connectionURI: "<CORE_API_ENDPOINT>",
    apiKey: "<YOUR_API_KEY>",
  },
  appInfo: {
    // learn more about this on https://supertokens.com/docs/references/backend-sdks/reference#sdk-configuration
    appName: "<YOUR_APP_NAME>",
    apiDomain: "<YOUR_API_DOMAIN>",
    websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
    apiBasePath: "/auth",
    websiteBasePath: "/auth",
  },
  recipeList: [
    ThirdParty.init({
      //...
    }),
    EmailPassword.init({
      //...
    }),
    Session.init(), // initializes session features
    UserMetadata.init(), // initializes the user metadata feature
  ],
});
import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import UserMetadata from "supertokens-node/recipe/usermetadata";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import EmailPassword from "supertokens-node/recipe/emailpassword";

supertokens.init({
  framework: "hapi",
  supertokens: {
    connectionURI: "<CORE_API_ENDPOINT>",
    apiKey: "<YOUR_API_KEY>",
  },
  appInfo: {
    // learn more about this on https://supertokens.com/docs/references/backend-sdks/reference#sdk-configuration
    appName: "<YOUR_APP_NAME>",
    apiDomain: "<YOUR_API_DOMAIN>",
    websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
    apiBasePath: "/auth",
    websiteBasePath: "/auth",
  },
  recipeList: [
    ThirdParty.init({
      //...
    }),
    EmailPassword.init({
      //...
    }),
    Session.init(), // initializes session features
    UserMetadata.init(), // initializes the user metadata feature
  ],
});
import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import UserMetadata from "supertokens-node/recipe/usermetadata";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import EmailPassword from "supertokens-node/recipe/emailpassword";

supertokens.init({
  framework: "fastify",
  supertokens: {
    connectionURI: "<CORE_API_ENDPOINT>",
    apiKey: "<YOUR_API_KEY>",
  },
  appInfo: {
    // learn more about this on https://supertokens.com/docs/references/backend-sdks/reference#sdk-configuration
    appName: "<YOUR_APP_NAME>",
    apiDomain: "<YOUR_API_DOMAIN>",
    websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
    apiBasePath: "/auth",
    websiteBasePath: "/auth",
  },
  recipeList: [
    ThirdParty.init({
      //...
    }),
    EmailPassword.init({
      //...
    }),
    Session.init(), // initializes session features
    UserMetadata.init(), // initializes the user metadata feature
  ],
});
import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import UserMetadata from "supertokens-node/recipe/usermetadata";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import EmailPassword from "supertokens-node/recipe/emailpassword";

supertokens.init({
  framework: "koa",
  supertokens: {
    connectionURI: "<CORE_API_ENDPOINT>",
    apiKey: "<YOUR_API_KEY>",
  },
  appInfo: {
    // learn more about this on https://supertokens.com/docs/references/backend-sdks/reference#sdk-configuration
    appName: "<YOUR_APP_NAME>",
    apiDomain: "<YOUR_API_DOMAIN>",
    websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
    apiBasePath: "/auth",
    websiteBasePath: "/auth",
  },
  recipeList: [
    ThirdParty.init({
      //...
    }),
    EmailPassword.init({
      //...
    }),
    Session.init(), // initializes session features
    UserMetadata.init(), // initializes the user metadata feature
  ],
});
import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import UserMetadata from "supertokens-node/recipe/usermetadata";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import EmailPassword from "supertokens-node/recipe/emailpassword";

supertokens.init({
  framework: "loopback",
  supertokens: {
    connectionURI: "<CORE_API_ENDPOINT>",
    apiKey: "<YOUR_API_KEY>",
  },
  appInfo: {
    // learn more about this on https://supertokens.com/docs/references/backend-sdks/reference#sdk-configuration
    appName: "<YOUR_APP_NAME>",
    apiDomain: "<YOUR_API_DOMAIN>",
    websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
    apiBasePath: "/auth",
    websiteBasePath: "/auth",
  },
  recipeList: [
    ThirdParty.init({
      //...
    }),
    EmailPassword.init({
      //...
    }),
    Session.init(), // initializes session features
    UserMetadata.init(), // initializes the user metadata feature
  ],
});
import (
	"github.com/supertokens/supertokens-golang/recipe/emailpassword"
	"github.com/supertokens/supertokens-golang/recipe/emailpassword/epmodels"
	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/thirdparty"
	"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
	"github.com/supertokens/supertokens-golang/recipe/usermetadata"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
    apiBasePath := "/auth"
    websiteBasePath := "/auth"
	err := supertokens.Init(supertokens.TypeInput{
		Supertokens: &supertokens.ConnectionInfo{
            ConnectionURI: "<CORE_API_ENDPOINT>",
            APIKey: "<YOUR_API_KEY>",
		},
		AppInfo: supertokens.AppInfo{
            AppName: "<YOUR_APP_NAME>",
            APIDomain: "<YOUR_API_DOMAIN>",
            WebsiteDomain: "<YOUR_WEBSITE_DOMAIN>",
            APIBasePath: &apiBasePath,
            WebsiteBasePath: &websiteBasePath,
		},
		RecipeList: []supertokens.Recipe{
			thirdparty.Init(&tpmodels.TypeInput{ /*...*/ }),
			emailpassword.Init(&epmodels.TypeInput{ /*...*/ }),
			session.Init(nil), // initializes session features
            usermetadata.Init(nil), // initializes the user metadata feature
		},
	})

	if err != nil {
		panic(err.Error())
	}
}
from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.recipe import thirdparty, emailpassword, session, usermetadata

init(
    app_info=InputAppInfo(
        app_name="<YOUR_APP_NAME>",
        api_domain="<YOUR_API_DOMAIN>",
        website_domain="<YOUR_WEBSITE_DOMAIN>",
        api_base_path="/auth",
        website_base_path="/auth"
    ),
    supertokens_config=SupertokensConfig(
        connection_uri="<CORE_API_ENDPOINT>",
        api_key="<YOUR_API_KEY>"
    ),
    framework='fastapi',
    recipe_list=[
	    session.init(), # initializes session features
        usermetadata.init(), # initializes the user metadata feature
        thirdparty.init(
           # ...
        ),
        emailpassword.init(
            # ...
        )
    ],
    mode='asgi' # use wsgi if you are running using gunicorn
)
from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.recipe import thirdparty, emailpassword, session, usermetadata

init(
    app_info=InputAppInfo(
        app_name="<YOUR_APP_NAME>",
        api_domain="<YOUR_API_DOMAIN>",
        website_domain="<YOUR_WEBSITE_DOMAIN>",
        api_base_path="/auth",
        website_base_path="/auth"
    ),
    supertokens_config=SupertokensConfig(
        connection_uri="<CORE_API_ENDPOINT>",
        api_key="<YOUR_API_KEY>"
    ),
    framework='flask',
    recipe_list=[
	    session.init(), # initializes session features
        usermetadata.init(), # initializes the user metadata feature
        thirdparty.init(
           # ...
        ),
        emailpassword.init(
            # ...
        )
    ]
)
from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.recipe import thirdparty, emailpassword, session, usermetadata

init(
    app_info=InputAppInfo(
        app_name="<YOUR_APP_NAME>",
        api_domain="<YOUR_API_DOMAIN>",
        website_domain="<YOUR_WEBSITE_DOMAIN>",
        api_base_path="/auth",
        website_base_path="/auth"
    ),
    supertokens_config=SupertokensConfig(
        connection_uri="<CORE_API_ENDPOINT>",
        api_key="<YOUR_API_KEY>"
    ),
    framework='django',
    recipe_list=[
	    session.init(), # initializes session features
        usermetadata.init(), # initializes the user metadata feature
        thirdparty.init(
           # ...
        ),
        emailpassword.init(
            # ...
        )
    ],
    mode='asgi' # use wsgi if you are running django server in sync mode
)

2. Adding second factor claim

After sign up or sign in of the first factor, the existence of the session signifies the completion of the first factor, but we want to explicitly mark the second factor as incomplete. This can be done by overriding the createNewSession function in the Session.init config:

import Session from "supertokens-node/recipe/session";
import { BooleanClaim } from "supertokens-node/recipe/session/claims";

/*
This will be used to modify the session's access token payload
to add {"2fa-completed": false} into it.
*/
export const SecondFactorClaim = new BooleanClaim({
  fetchValue: () => false,
  key: "2fa-completed",
});

Session.init({
  override: {
    functions: (originalImplementation) => {
      return {
        ...originalImplementation,
        /* This function is called after signing in or signing up via the first factor */
        createNewSession: async function (input) {
          return originalImplementation.createNewSession({
            ...input,
            accessTokenPayload: {
              ...input.accessTokenPayload,
              ...(await SecondFactorClaim.build(
                input.userId,
                input.recipeUserId,
                input.tenantId,
                undefined,
                input.userContext,
              )),
            },
          });
        },
      };
    },
  },
});
import (
	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/claims"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
	SecondFactorClaim, _ := claims.BooleanClaim("2fa-completed", func(userId, tenantId string, userContext supertokens.UserContext) (interface{}, error) {
		return false, nil
	}, nil)

	session.Init(&sessmodels.TypeInput{
		Override: &sessmodels.OverrideStruct{
			Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
				oCreateNewSession := *originalImplementation.CreateNewSession
				/* This function is called after signing in or signing up via the first factor */
				(*originalImplementation.CreateNewSession) = func(userID string, accessTokenPayload, sessionDataInDatabase map[string]interface{}, disableAntiCsrf *bool, tenantId string, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
					if accessTokenPayload == nil {
						accessTokenPayload = map[string]interface{}{}
					}
					accessTokenPayload, err := SecondFactorClaim.Build(userID, tenantId, accessTokenPayload, userContext)
					if err != nil {
						return nil, err
					}
					return oCreateNewSession(userID, accessTokenPayload, sessionDataInDatabase, disableAntiCsrf, tenantId, userContext)
				}
				return originalImplementation
			},
		},
	})
}
from supertokens_python.recipe import session
from supertokens_python.recipe.session.claims import BooleanClaim
from supertokens_python.recipe.session.interfaces import RecipeInterface
from typing import Any, Dict, Optional
from supertokens_python.types import RecipeUserId

# This will be used to modify the session's access token payload
# to add {"2fa-completed": false} into it.
SecondFactorClaim = BooleanClaim(
    key="2fa-completed", fetch_value=lambda _, __, ___, ____, _____: False
)


def override_session_functions(original_implementation: RecipeInterface):
    original_create_new_session = original_implementation.create_new_session

    async def create_new_session(
        user_id: str,
        recipe_user_id: RecipeUserId,
        access_token_payload: Optional[Dict[str, Any]],
        session_data_in_database: Optional[Dict[str, Any]],
        disable_anti_csrf: Optional[bool],
        tenant_id: str,
        user_context: Dict[str, Any],
    ):
        # This function is called after signing in or signing up via the first factor

        if access_token_payload is None:
            access_token_payload = {}

        access_token_payload = {
            **access_token_payload,
            **(
                await SecondFactorClaim.build(
                    user_id,
                    recipe_user_id,
                    tenant_id,
                    access_token_payload,
                    user_context,
                )
            ),
        }
        return await original_create_new_session(
            user_id,
            recipe_user_id,
            access_token_payload,
            session_data_in_database,
            disable_anti_csrf,
            tenant_id,
            user_context,
        )

    original_implementation.create_new_session = create_new_session
    return original_implementation


session.init(
    override=session.InputOverrideConfig(functions=override_session_functions)
)

We add SecondFactorClaim into the access token payload. This will be set to false on session creation (see fetchValue in the claim definition).

API reference

API schema and response details