Skip to content
Esc
navigateopen⌘Jpreview
Dashboard
On this page

Implement anonymous sessions

Track user actions with anonymous sessions, create and verify JWTs, and transfer data post-login.

Overview

With anonymous sessions, you can keep track of user’s action / data before they login, and then transfer that data to their post login session.

Anonymous sessions have different properties than regular, logged in sessions:

  • The userID of anonymous sessions doesn’t matter
  • The security constraints on anonymous sessions are lesser than regular sessions, as you would want users to log in before doing anything sensitive anyway.
  • Each visitor that visits your app / website gets an anonymous session if they don’t have one previously. This does not require them to log in.
  • Anonymous sessions are not stored in the database to avoid the risk of flooding the database with sessions that are not useful to the app.

Given the different characteristics of anonymous sessions, using a simple, long lived JWT is a perfect use case. They can store any information about the user’s activity, and they don’t occupy any database space either.

Steps

1. Create the JWT

Start by creating a JWT like in the next example:

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

async function createAnonymousJWT(payload: any) {
  let jwtResponse = await Session.createJWT(
    {
      key: "value",
      // more payload...
    },
    315360000,
  ); // 10 years lifetime
  if (jwtResponse.status === "OK") {
    // Send JWT as Authorization header to M2
    return jwtResponse.jwt;
  }
  throw new Error("Unable to create JWT. Should never come here.");
}
import (
	"fmt"

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

func main() {
	validitySessions := uint64(315360000)
	jwtResponse, err := session.CreateJWT(map[string]interface{}{
		"key": "value",
		// ...additional payload
	}, &validitySessions, nil) // 10 years lifetime
	if err != nil {
		// handle error
	}
	jwtString := jwtResponse.OK.Jwt
	fmt.Println(jwtString)
	// Send JWT as Authorization header to M2
}
from supertokens_python.recipe.session import asyncio
from supertokens_python.recipe.jwt.interfaces import CreateJwtOkResult

async def create_jwt():
    jwtResponse = await asyncio.create_jwt({
        "key": "value",
        # ... extra payload
    }, 315360000) # 10 years lifetime

    if isinstance(jwtResponse, CreateJwtOkResult):
        _ = jwtResponse.jwt
        # Send JWT as Authorization header to M2
    else:
        raise Exception("Unable to create JWT. Should never come here.")
from supertokens_python.recipe.session.syncio import create_jwt
from supertokens_python.recipe.jwt.interfaces import CreateJwtOkResult

jwtResponse = create_jwt({
    "source": "microservice",
    # ... extra payload
})

if isinstance(jwtResponse, CreateJwtOkResult):
    jwtStr = jwtResponse.jwt
    # Send JWT as Authorization header to M2
else:
    raise Exception("Unable to create JWT. Should never come here.")
  • As shown in the code above, you can add any payload you like to the JWT. You can even add a sub (userId) payload with a random UUID if you like, or some user ID with a prefix like "G-.." which indicates this is a guest user ID.
  • You could create your own application middleware which inspects the request and auto adds a JWT to it in the response cookies. This way, whenever a user visits your website and makes an API call, they get a JWT in their cookies, and you can use that JWT to track their activity.

2. Send the JWT to the client

After creating the JWT, you can send it to a user as a cookie. This way you can use it to track the activity during a session.

Verify the JWT

To check if an anonymous session is valid read through the manual JWT verification section. On thing to note here is that in the section about verification using the public key string, you do not need to set useDynamicAccessTokenSigningKey to true. The token creation process in this scenario uses the static signing key (kid starting the s-..) by default.

3. Transfer the data to a logged in session

The idea here is to override the Session recipe on the backend. Whenever the user logs in or signs up, the data from the anonymous session transfers to the logged-in session.

The override attempts to read the request header cookie to get the JWT. It assumes that you have added it to the cookies, verifies it, and then adds the payload to the logged-in session.

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

SuperTokens.init({
  supertokens: {
    connectionURI: "...",
  },
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [
    // ...
    Session.init({
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,
            createNewSession: async function (input) {
              let userId = input.userId;

              const request = SuperTokens.getRequestFromUserContext(input.userContext);

              let jwt: string | undefined;
              let jwtPayload = {};

              if (request !== undefined) {
                jwt = request.getCookieValue("jwt");
              } else {
                /**
                 * This is possible if the function is triggered from the user management dashboard
                 *
                 * In this case because we cannot read the JWT, we create a session without the custom
                 * payload properties
                 */
              }

              if (jwt !== undefined) {
                // verify JWT using a JWT verification library..

                jwtPayload = {
                  /* ... get from decoded jwt ... */
                };
              }

              // This goes in the access token, and is available to read on the frontend.
              input.accessTokenPayload = {
                ...input.accessTokenPayload,
                ...jwtPayload,
              };

              return originalImplementation.createNewSession(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() {
	supertokens.Init(supertokens.TypeInput{
		RecipeList: []supertokens.Recipe{
			session.Init(&sessmodels.TypeInput{
				Override: &sessmodels.OverrideStruct{
					Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
						// First we copy the original implementation func
						originalCreateNewSession := *originalImplementation.CreateNewSession

						// Now we override the CreateNewSession function
						(*originalImplementation.CreateNewSession) = func(userID string, accessTokenPayload, sessionDataInDatabase map[string]interface{}, disableAntiCsrf *bool, tenantId string, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
                            request := supertokens.GetRequestFromUserContext(userContext)

                            if (request != nil) {
                                jwt, err := request.Cookie("jwt")

                                if err != nil {
                                    return nil, err
                                }

                                if jwt != nil {
                                    // verify JWT using a jwt verification library..
                                    decodedJWT := map[string]interface{}{
                                        // from JWT verification lib
                                    }

                                    // This goes in the access token, and is available to read on the frontend.
                                    if accessTokenPayload == nil {
                                        accessTokenPayload = map[string]interface{}{}
                                    }
                                    accessTokenPayload["someKey"] = decodedJWT["someKey"]
                                }
                            } else {
                                /**
                                * This is possible if the function is triggered from the user management dashboard
                                *
                                * In this case because we cannot read the JWT, we create a session without the custom
                                * payload properties
                                */
                            }

							return originalCreateNewSession(userID, accessTokenPayload, sessionDataInDatabase, disableAntiCsrf, tenantId, userContext)
						}

						return originalImplementation
					},
				},
			}),
		},
	})
}
from supertokens_python import init, InputAppInfo, get_request_from_user_context
from supertokens_python.recipe import session
from supertokens_python.recipe.session.interfaces import RecipeInterface
from typing import Any, Dict, Optional
from supertokens_python.types import RecipeUserId


def override_functions(original_implementation: RecipeInterface):
    original_implementation_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],
    ):

        request = get_request_from_user_context(user_context)
        jwt: Optional[str] = None

        if request is not None:
            jwt = request.get_cookie("jwt")
        else:
            #
            # This is possible if the function is triggered from the user management dashboard
            #
            # In this case because we cannot read the JWT, we create a session without the custom
            # payload properties
            #
            jwt = None

        if jwt is not None:
            # verify JWT using a JWT verification library..
            jwt_payload = {
                # from JWT verification lib
            }

            # This goes in the access token, and is available to read on the frontend.
            if access_token_payload is None:
                access_token_payload = {}
            access_token_payload["someKey"] = jwt_payload["someKey"]

        return await original_implementation_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(
    app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
    framework="...",
    recipe_list=[
        session.init(
            override=session.InputOverrideConfig(functions=override_functions)
        )
    ],
)
  • In the above code snippet, the system reads the JWT from the request header cookie. If it exists, it verifies it and then adds the payload to the logged-in session.
  • If the JWT doesn’t exist, or if the system cannot verify it, it would be safe to ignore it and create the logged-in session anyway.
  • We assume that the you have saved the JWT in the cookies in with the key of jwt. But if not, you can remove the JWT from the request based on how you have saved it. You can even read the headers from the request.

API reference

API schema and response details