Skip to main content

User context

Overview

The UserContext mechanism is a way to pass information across recipe or API functions to customize the authentication flow inside a specific execution context.

By default, the user context passed to APIs and functions contains the request object that can read custom headers, body, or query parameters.

Prerequisites

Important

This feature is only available for SDKs versions:

  • NodeJS >= v9.0
  • Python >= v0.5
  • GoLang >= v0.5

Example

For example, you may want to disable creation of a session during sign up, ensuring that the user has to login again post sign up. To do that, the create new session recipe function must know that it's called from the sign up API and return an empty session. This is as opposed to it invoking from the sign in API, when it should continue with normal functionality.

To achieve this, all the API interface and recipe interface functions take a parameter called userContext, which is by default an empty object. When overriding the functions, you can add anything in this object, and that information carries onto the next set of functions called in the API.

import SuperTokens from "supertokens-node";
import ThirdParty from "supertokens-node/recipe/thirdparty"
import EmailPassword from "supertokens-node/recipe/emailpassword"

SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
EmailPassword.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
signUp: async function (input) {
let resp = await originalImplementation.signUp(input);
if (resp.status === "OK" && resp.user.loginMethods.length === 1 && input.session === undefined) {
/*
* This is called during the sign up API for email password login,
* but before calling the createNewSession function.
* We override the recipe function as shown here,
* and then set the relevant context only if it's a new user.
*/
input.userContext.isSignUp = true;
}
return resp;
},
}
},
}
}),
ThirdParty.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
signInUp: async function (input) {
let resp = await originalImplementation.signInUp(input);
if (resp.status === "OK" && resp.createdNewRecipeUser && resp.user.loginMethods.length === 1 && input.session === undefined) {
/*
* This is called during the signInUp API for third party login,
* but before calling the createNewSession function.
* At the start of the API, we do not know if it will result in a
* sign in or a sign up, so we cannot override the API function.
* Instead, we override the recipe function as shown here,
* and then set the relevant context only if it's a new user.
*/
input.userContext.isSignUp = true;
}
return resp;
},
}
},
}
})
]
});

Then consume that context in the createNewSession function to return an empty function in case the userContext.isSignUp is true.

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

SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
ThirdParty.init({
/* See previous step... */
}),
EmailPassword.init({
/* See previous step... */
}),
Session.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
createNewSession: async function (input) {
if (input.userContext.isSignUp) {
/**
* The execution will come here only in case
* a sign up API is calling this function. This is because
* only then will the input.userContext.isSignUp === true
* (see above code).
*/
return { // this is an empty session. It won't result in a session being created for the user.
getAccessToken: () => "",
getAccessTokenPayload: () => null,
getExpiry: async () => -1,
getHandle: () => "",
getSessionDataFromDatabase: async () => null,
getTimeCreated: async () => -1,
getUserId: () => "",
revokeSession: async () => { },
updateSessionDataInDatabase: async () => { },
mergeIntoAccessTokenPayload: async () => { },
assertClaims: async () => { },
fetchAndSetClaim: async () => { },
getClaimValue: async () => undefined,
setClaimValue: async () => { },
removeClaim: async () => { },
attachToRequestResponse: () => { },
getAllSessionTokensDangerously: () => ({
accessAndFrontTokenUpdated: false,
accessToken: "",
frontToken: "",
antiCsrfToken: undefined,
refreshToken: undefined,
}),
getTenantId: () => "public",
getRecipeUserId: () => SuperTokens.convertToRecipeUserId(""),
};
}
return originalImplementation.createNewSession(input);
}
}
}
}
})
]
});

As a summary, when the sign up API invokes, the initial value of userContext is an empty object. Change that user context to add the isSignUp field, ensuring that information communicates to the createNewSession function.

When that function invokes, it checks if isSignUp === true, and if it is, it doesn't call the original implementation, and instead, returns an empty session. This way, the system does not create a session if the user is signing up, but it creates one if the user is signing in.

Note that there are other ways of achieving this, but the above showcases how user context can use to communicate across recipes and across API & Recipe functions.