Anonymous / Guest sessions
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 is lesser than regular sessions, cause you would want users to be logged 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 be logged in.
- We do not want to store anonymous sessions in the database since we run the risk of flodding the db with several sessions that are not that useful to the app.
Given the different characteristics of anonymous sessions, using a simple, long lived JWT is a perfect use case. They can be used to store any information about user's activity, and they don't occupy any database space either.
#
Creating a JWTWe can issue JWTs using the Session recipe's Session.createJWT
function as shown below:
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
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
}
- Asyncio
- Syncio
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 will inspects the request and auto adds a JWT to it in the response cookies. This way, whenever a user visits your website, and make an API call, they will get a JWT in their cookies, and you can use that JWT to track their activity.
#
Verifying the JWTcaution
Please note that you cannot use the verifySession
or getSession
functions to verify JWTs from anonymous sessions. The verifySession
and getSession
check for the presence of certain claims in the JWT (sessionHandle
, refreshTokenHash
etc...) that are added by the Session recipe for authenticated users. Verification for JWTs from anonymous sessions have to be done manually..
See the manual JWT verification section to see how we can verify the JWT and get its payload.
important
In the section about verification using the public key string, we do not need to set useDynamicAccessTokenSigningKey
to true
since the createJWT
function used above uses the static signing key (kid
starting the s-..
) by default.
#
Updating the JWT's payloadYou can create a new JWT with the new payload and add it to the response cookies. This way, the user will have the new JWT in their cookies, and you can use that JWT to track their activity.
#
Transferring data to a logged in sessionThe idea here is that we will override the createNewSession
on the backend's Session.init
so that whenever the user logs in / signs up, we can transfer the data from the anonymous session to the logged in session.
Our override will attempt to read the request header cookie to get the JWT (assuming that you have added it to the cookies), verify it, and then add the payload to the logged in session.
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
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, we attempt to read the JWT from the request header cookie. If it exists, we verify it and then add the payload to the logged in session.
- If the JWT doesn't exist, or if we 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 extract the JWT from the request based on how you have saved it. You can even read the headers from the request. The full interface for the request object is:- For nodejs
- For go: The request object type is
*http.Request
- For python