Skip to content
Esc
navigateopen⌘Jpreview
Dashboard
On this page

Function Overrides

Override backend authentication functions to cover custom use cases.

Overview

Function overrides let you customize the behavior of the functions used internally, by the SDKs. You can change how actions like signing in, signing up, creating, or revoking sessions or signing out work. This flexibility lets you integrate your own logic into the authentication and session management processes.

For example, if a recipe checks for an active session using the session recipe’s doesSessionExist function, you can override that function to work with custom session management. Similarly, if you already have a sign-in/sign-up flow and want to integrate with SuperTokens, you can use an override to handle the migration process. You can even implement a custom userId format by mapping userIds to those generated by SuperTokens.

Example

The code snippet shows the general flow of overriding a function. Custom logic can be injected while also calling the original implementation of the function.

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

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  supertokens: {
    connectionURI: "...",
  },
  recipeList: [
    Session.init({
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,

            // here we are only overriding the function that's responsible
            // for creating a new session
            createNewSession: async function (input) {
              // TODO: some custom logic

              // or call the default behaviour as show below
              return await originalImplementation.createNewSession(input);
            },
            // ...
            // TODO: override more functions
          };
        },
      },
    }),
    ThirdParty.init({
      signInAndUpFeature: {
        providers: [
          /* ... */
        ],
      },
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,

            // here we are only overriding the function that's responsible
            // for signing in or signing up a user.
            signInUp: async function (input) {
              // TODO: some custom logic

              // or call the default behaviour as show below
              return await originalImplementation.signInUp(input);
            },
            // ...
            // TODO: override more functions
          };
        },
      },
    }),
  ],
});
import (
	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
	"github.com/supertokens/supertokens-golang/recipe/thirdparty"	
	"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
	supertokens.Init(supertokens.TypeInput{
		RecipeList: []supertokens.Recipe{
			session.Init(&sessmodels.TypeInput{
				Override: &sessmodels.OverrideStruct{
					Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
						// First we make a copy of the original implementation
						originalCreateNewSession := *originalImplementation.CreateNewSession

						// Then we override the default impl
						(*originalImplementation.CreateNewSession) = func(userID string, accessTokenPayload, sessionDataInDatabase map[string]interface{}, disableAntiCsrf *bool, tenantId string, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
							// TODO: some custom logic

							// or call the default behaviour as show below
							return originalCreateNewSession(userID, accessTokenPayload, sessionDataInDatabase, disableAntiCsrf, tenantId, userContext)
						}

						return originalImplementation
					},
				},
			}),
			thirdparty.Init(&tpmodels.TypeInput{
				Override: &tpmodels.OverrideStruct{
					Functions: func(originalImplementation tpmodels.RecipeInterface) tpmodels.RecipeInterface {

						//First we copy the original impl
						originalSignInUp := *originalImplementation.SignInUp

						// Then we override the functions we want to
						(*originalImplementation.SignInUp) = func(thirdPartyID string, thirdPartyUserID string, email string, oAuthTokens map[string]interface{}, rawUserInfoFromProvider tpmodels.TypeRawUserInfoFromProvider, tenantId string, userContext *map[string]interface{}) (tpmodels.SignInUpResponse, error) {
							// TODO: some custom logic

							// or call the default behaviour as show below
							return originalSignInUp(thirdPartyID, thirdPartyUserID, email, oAuthTokens, rawUserInfoFromProvider, tenantId, userContext)
						}

						// TODO: Override more functions

						return originalImplementation
					},
				},
			}),
		},
	})
}
from typing import Any, Dict, Optional, Union

from supertokens_python import InputAppInfo, SupertokensConfig, init
from supertokens_python.recipe import session, thirdparty
from supertokens_python.recipe.session.interfaces import (
    RecipeInterface as SessionRecipeInterface,
)
from supertokens_python.recipe.session.interfaces import SessionContainer
from supertokens_python.recipe.thirdparty.interfaces import (
    RecipeInterface as ThirdPartyRecipeInterface,
)
from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider
from supertokens_python.types import RecipeUserId


def override_thirdparty_functions(original_implementation: ThirdPartyRecipeInterface):
    original_sign_in_up = original_implementation.sign_in_up

    async def sign_in_up(
        third_party_id: str,
        third_party_user_id: str,
        email: str,
        is_verified: bool,
        oauth_tokens: Dict[str, Any],
        raw_user_info_from_provider: RawUserInfoFromProvider,
        session: Optional[SessionContainer],
        should_try_linking_with_session_user: Union[bool, None],
        tenant_id: str,
        user_context: Dict[str, Any],
    ):
        # TODO: custom logic

        # or call the default behaviour as show below
        return await original_sign_in_up(
            third_party_id,
            third_party_user_id,
            email,
            is_verified,
            oauth_tokens,
            raw_user_info_from_provider,
            session,
            should_try_linking_with_session_user,
            tenant_id,
            user_context,
        )

    original_implementation.sign_in_up = sign_in_up
    return original_implementation

def override_session_functions(original_implementation: SessionRecipeInterface):
    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],
    ):
        # TODO: custom logic

        # or call the default behaviour as show below
        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


init(
    supertokens_config=SupertokensConfig(connection_uri="..."),
    app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
    framework="fastapi",
    recipe_list=[
        session.init(
            override=session.InputOverrideConfig(functions=override_session_functions)
        ),
        thirdparty.init(
            override=thirdparty.InputOverrideConfig(
                functions=override_thirdparty_functions
            ),
            sign_in_and_up_feature=thirdparty.SignInAndUpFeature(
                providers=[
                    # ...
                ]
            ),
        )
    ],
)

Error management

If you want to throw a custom error from function overrides you have to handle it manually.

Raise the error

import Session from "supertokens-node/recipe/session";

Session.init({
  override: {
    functions: (originalImplementation) => {
      return {
        ...originalImplementation,
        createNewSession: async function (input) {
          const existingSessions = await Session.getAllSessionHandlesForUser(input.userId);
          if (existingSessions.length > 0) {
            // this means that the user already has a session on some other device
            throw new Error("Session already exists on another device");
          }

          // no other session exists, and so we can continue with logging in this user
          return originalImplementation.createNewSession(input);
        },
      };
    },
  },
});
import (
	"errors"

	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
	session.Init(&sessmodels.TypeInput{
		Override: &sessmodels.OverrideStruct{
			Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {

				// first we copy the original implementation
				originalCreateNewSession := *originalImplementation.CreateNewSession

				(*originalImplementation.CreateNewSession) = func(userID string, accessTokenPayload, sessionDataInDatabase map[string]interface{}, disableAntiCsrf *bool, tenantId string, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
					existingSessions, err := session.GetAllSessionHandlesForUser(userID, &tenantId, userContext)
					if err != nil {
						return nil, err
					}

					if len(existingSessions) > 0 {
						// this means that the user already has a session on some other device
						return nil, errors.New("Session already exists on another device")
					}

					// no other session exists, and so we can continue with logging in this user
					return originalCreateNewSession(userID, accessTokenPayload, sessionDataInDatabase, disableAntiCsrf, tenantId, userContext)
				}

				return originalImplementation

			},
		},
	})
}
from typing import Any, Dict, Optional

from supertokens_python.recipe import session
from supertokens_python.recipe.session.asyncio import get_all_session_handles_for_user
from supertokens_python.recipe.session.interfaces import RecipeInterface
from supertokens_python.types import RecipeUserId


def override_session_functions(original_implementation: RecipeInterface):

    # first we copy the original implementation
    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],
    ):
        existing_sessions = await get_all_session_handles_for_user(user_id)

        if len(existing_sessions) > 0:
            # this means that the user already has a session on some other device
            raise Exception("Session already exists on another device")

        # no other session exists, and so we can continue with logging in this user
        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))

Handle the error manually

Next.js router
import express from "express";

let app = express();

//...

// in your app's error handler, we catch the custom error
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  if (err.message === "Session already exists on another device") {
    // TODO: send a custom response using res
    return;
  }
  res.status(500).send(err.message);
});
import Hapi from "@hapi/hapi";
let server = new Hapi.Server({ port: 8000 });

// first we create a plugin to handle all errors from the app
const plugin = {
  name: "...",
  version: "...",
  register: async function (server: Hapi.Server) {
    server.ext("onPreResponse", async (request, h) => {
      if ("isBoom" in request.response) {
        let err = request.response.data;
        if (err.message === "Session already exists on another device") {
          // TODO: send a custom response here with takeover
        }
      }
      return h.continue;
    });
  },
};

// then we register this plugin
(async () => {
  await server.register(plugin);

  await server.start();
})();
import Fastify from "fastify";

let fastify = Fastify();

fastify.setErrorHandler(async (err: any, req, res) => {
  if (err.message === "Session already exists on another device") {
    // TODO: send a custom response here with takeover
  }
  // TODO: send a 500 error with the err.message
});
import middy from "@middy/core";
import cors from "@middy/http-cors";
import SuperTokens from "supertokens-node";
// this is in the auth.js file
import { middleware } from "supertokens-node/framework/awsLambda";
import { getBackendConfig } from "./config";

module.exports.handler = middy(middleware())
  .use(
    cors({
      origin: getBackendConfig().appInfo.websiteDomain,
      credentials: true,
      headers: ["Content-Type", ...SuperTokens.getAllCORSHeaders()].join(", "),
      methods: "OPTIONS,POST,GET,PUT,DELETE",
    }),
  )
  .onError((request) => {
    if (request.error !== null && request.error.message === "Session already exists on another device") {
      // TODO: send a custom response here with takeover
    }
    throw request.error;
  });
import Koa from "koa";
import { middleware } from "supertokens-node/framework/koa";

let app = new Koa();

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err: any) {
    if (err.message === "Session already exists on another device") {
      // TODO: return a custom response
    }
    throw err;
  }
});

app.use(middleware());
import { Next } from "@loopback/core";
import { RestApplication, Middleware, MiddlewareContext } from "@loopback/rest";
import { middleware } from "supertokens-node/framework/loopback";

let app = new RestApplication();

export const customErrorMiddleware: Middleware = async (ctx: MiddlewareContext, next: Next) => {
  try {
    return await next();
  } catch (err: any) {
    if (err.message === "Session already exists on another device") {
      // TODO: return a custom response
    }
    throw err;
  }
};

app.middleware(middleware);
app.middleware(customErrorMiddleware);
import { ExceptionFilter, Catch, ArgumentsHost } from "@nestjs/common";

import { errorHandler } from "supertokens-node/framework/express";
import { Error as STError } from "supertokens-node";

// we want to add our own error handler which will catch the special exception

@Catch(STError)
export class AppErrorHandler implements ExceptionFilter {
  catch(exception: Error, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    if (exception.message === "Session already exists on another device") {
      // TODO: send custom error using ctx.getResponse<Response>()
    } else {
      throw exception;
    }
  }
}
import (
	"net/http"

	"github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
	supertokens.Init(supertokens.TypeInput{
		OnSuperTokensAPIError: func(err error, req *http.Request, res http.ResponseWriter) {
			if err.Error() == "Session already exists on another device" {
				// TODO: send custom error
			}
			// TODO: send generic error
		},
	})
}
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(Exception)
async def exception_handler(_: Request, exc: Exception) -> JSONResponse:
    if str(exc) == "Session already exists on another device":
        return JSONResponse({"message": str(exc)}, status_code=409)

    return JSONResponse({"message": "Internal server error"}, status_code=500)
from flask import Flask

app = Flask(__name__)

@app.errorhandler(Exception)
def all_exception_handler(error: Exception):
    if str(error) == "Session already exists on another device":
        return {"message": str(error)}, 409

    return {"message": "Internal server error"}, 500
# Add this middlware in settings.py

from typing import Callable

from django.http import HttpRequest, HttpResponse


class ErrorHandlerMiddleware:

    def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
        self.get_response = get_response

    def __call__(self, request: HttpRequest):
        response = self.get_response(request)
        return response

    def process_exception(self, request: HttpRequest, exception: Exception) -> HttpResponse:
        if exception and str(exception) == "Session already exists on another device":
            pass # TODO: send custom response

        return HttpResponse("Error processing the request.", status=500)

Read custom request information

We use the getRequestFromUserContext function provided by the SDK to get the request object from the user context.

We use the GetRequestFromUserContext function provided by the SDK to get the request object from the user context.

We use the get_request_from_user_context function provided by the SDK to get the request object from the user context.

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

Session.init({
  override: {
    functions: (oI) => {
      return {
        ...oI,
        revokeSession: async (input) => {
          let customHeaderValue = "";
          const request = SuperTokens.getRequestFromUserContext(input.userContext);

          if (request !== undefined) {
            customHeaderValue = request.getHeaderValue("customHeader") ?? "";
          } else {
            /**
             * This is possible if the function is triggered from the user management dashboard
             *
             * In this case set a reasonable default value to use
             */
            customHeaderValue = "default";
          }

          // Perform custom logic based on the value of customHeaderValue

          return oI.revokeSession(input);
        },
      };
    },
  },
});
import (
    "github.com/supertokens/supertokens-golang/recipe/session"
    "github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
    "github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
    session.Init(&sessmodels.TypeInput{
        Override: &sessmodels.OverrideStruct{
            Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
                originalRevokeSession := *originalImplementation.RevokeSession

                *originalImplementation.RevokeSession = func(sessionHandle string, userContext supertokens.UserContext) (bool, error) {
                    customHeadervalue := ""
                    request := supertokens.GetRequestFromUserContext(userContext)

                    if request != nil {
                        customHeadervalue = request.Header.Get("customHeader")
                    } else {
                        /**
                        * This is possible if the function is triggered from the user management dashboard
                        * 
                        * In this case set a reasonable default value to use
                        */
                        customHeadervalue = "default";
                    }

                    print(customHeadervalue)

                    // Perform custom logic based on the value of customHeadervalue

                    return originalRevokeSession(sessionHandle, userContext)
                }

                return originalImplementation
            },
        },
    })
}
from typing import Any, Dict

from supertokens_python import get_request_from_user_context
from supertokens_python.recipe import session
from supertokens_python.recipe.session.interfaces import RecipeInterface


def override_session_functions(original_implementation: RecipeInterface):
    original_revoke_session = original_implementation.revoke_session

    async def revoke_session(session_handle: str, user_context: Dict[str, Any]):
        request=get_request_from_user_context(user_context)
        customHeaderValue=""

        if request is not None:
            customHeaderValue=request.get_header("customHeader")
        else:
            #
            # This is possible if the function is triggered from the user management dashboard
            #
            # In this case set a reasonable default value to use
            #
            customHeaderValue="default"

        print(customHeaderValue)
        # Perform custom logic based on the value of customHeadervalue

        return await original_revoke_session(session_handle, user_context)

    original_implementation.revoke_session = revoke_session
    return original_implementation


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

API reference

API schema and response details