Skip to main content

User Context

important

This feature is only available for SDKs versions:

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

How does it work?#

This is a powerful concept that allows you to pass information across recipe and / or API functions so that customisations can be made based on a specific "execution context".

For example, you may want to disable creation of a session during sign up so that the user has to login again post sign up. In order to do that, the createNewSession recipe function (from the Session recipe) will have to know that it's being called from the sign up API and return an empty session (which is equal to no session). This is as opposed to it being called from the sign in API, in which it should continue with normal functionalty.

In order 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 is carried onto the next set of functions being called in the API

Example use#

Let's take the example mentioned above and implement it in the context of this recipe. First, we override the sign up APIs to add information into the context indicating that it's a sign up API call:

import SuperTokens from "supertokens-node";
import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword";
import Session from "supertokens-node/recipe/session";

SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
ThirdPartyEmailPassword.init({
override: {
apis: (originalImplementation) => {
return {
...originalImplementation,
// override sign up using email / password
emailPasswordSignUpPOST: async function (input) {
if (originalImplementation.emailPasswordSignUpPOST === undefined) {
throw new Error("Should never come here");
}

// by default, the userContext object is {},
// we change it to {isSignUp: true}, since this is one of the
// sign up API, and this will tell the createNewSession function
// (being called inside originalImplementation.emailPasswordSignUpPOST)
// to not create a new session in case userContext.isSignUp === true
input.userContext.isSignUp = true;
return originalImplementation.emailPasswordSignUpPOST(input);
},
}
},
functions: (originalImplementation) => {
return {
...originalImplementation,
thirdPartySignInUp: async function (input) {
let resp = await originalImplementation.thirdPartySignInUp(input);
if (resp.status === "OK" && resp.createdNewUser) {
/*
* 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 we 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 ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword";
import Session from "supertokens-node/recipe/session";

SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
ThirdPartyEmailPassword.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"
};
}
return originalImplementation.createNewSession(input);
}
}
}
}
})
]
});

As a summary, when the sign up API is called, the initial value of userContext is an empty object. We change that user context to add the isSignUp field so that that information can be communicated to the createNewSession function.

When that is called, that function checks if isSignUp === true, and if it is, it doesn't call the original implementation, and instead, just returns an empty session. This way, we don't create a session if the user is signing up, but we do create one if the user is signing in.

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

Default information in the user context object#

By default the user context passed to APIs and functions contains the request object that can be used to read custom headers, body/query params etc.

To learn more on how you can use the default user context to consume custom request information visit this page.