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).