Manage user sessions
User sessions determine how long users stay signed in to your application. After users successfully authenticate, you receive session tokens that manage their access. These tokens control session duration, multi-device access, and cross-product authentication within your company’s ecosystem.
Scalekit provides session management capabilities out of the box. Your application receives an access_token
and refresh_token
in the authentication response. This guide shows you how to store these tokens securely and refresh them before they expire.
-
Store session tokens securely
Section titled “Store session tokens securely”After successful user authentication, your application receives session tokens in the response object.
Express.js const authResult = await scalekit.authenticateWithCode(code, redirectUri);// authResult contains user profile and session tokensconst { user, accessToken, refreshToken, expiresIn } = authResult;Flask auth_result = scalekit_client.authenticate_with_code(code=code,redirect_uri=redirect_uri)# auth_result contains user profile and session tokensuser = auth_result.useraccess_token = auth_result.access_tokenrefresh_token = auth_result.refresh_tokenexpires_in = auth_result.expires_inGin authResult, err := scalekitClient.AuthenticateWithCode(code,redirectUri,)if err != nil {// Handle error}// authResult contains user profile and session tokensuser := authResult.UseraccessToken := authResult.AccessTokenrefreshToken := authResult.RefreshTokenexpiresIn := authResult.ExpiresInSpring AuthResult authResult = scalekitClient.authentication().authenticateWithCode(code,redirectUri);// authResult contains user profile and session tokensUser user = authResult.getUser();String accessToken = authResult.getAccessToken();String refreshToken = authResult.getRefreshToken();int expiresIn = authResult.getExpiresIn();Store each token based on its security requirements:
- Access Token: Store in a secure, HTTP-only cookie to prevent XSS attacks. This token has a short lifespan and provides access to protected resources.
- Refresh Token: Store in your backend database or secure server-side storage. This long-lived token generates new access tokens.
Here’s how to set secure cookies:
Express.js import cookieParser from 'cookie-parser';// Configure cookie parser middlewareapp.use(cookieParser());// After receiving the authResult from scalekit.authenticateWithCode()const { accessToken, expiresIn, refreshToken, user } = authResult;// Store the refresh token in your databaseawait db.saveRefreshToken(user.id, refreshToken);// Set the access token as a secure cookieres.cookie('accessToken', accessToken, {maxAge: (expiresIn - 60) * 1000, // Convert to milliseconds, subtract 60s bufferhttpOnly: true,secure: process.env.NODE_ENV === 'production',sameSite: 'strict'});Flask from flask import Flask, make_responseimport osapp = Flask(__name__)# After receiving the auth_result from scalekit.authenticate_with_code()access_token = auth_result.access_tokenexpires_in = auth_result.expires_inrefresh_token = auth_result.refresh_tokenuser = auth_result.user# Store the refresh token in your databasedb.save_refresh_token(user.id, refresh_token)# Set the access token as a secure cookieresponse = make_response()response.set_cookie('accessToken',access_token,max_age=(expires_in - 60) * 1000, # Convert to milliseconds, subtract 60s bufferhttponly=True,secure=os.environ.get('FLASK_ENV') == 'production',samesite='Strict')Gin import ("net/http""os""time""github.com/gin-gonic/gin")// After receiving the authResult from scalekit.AuthenticateWithCode()accessToken := authResult.AccessTokenexpiresIn := authResult.ExpiresInrefreshToken := authResult.RefreshTokenuser := authResult.User// Store the refresh token in your databasedb.SaveRefreshToken(user.ID, refreshToken)// Set the access token as a secure cookiec.SetCookie("accessToken",accessToken,(expiresIn-60)*1000, // Convert to milliseconds, subtract 60s buffer"/","",os.Getenv("GIN_MODE") == "release",true, // httpOnly)c.SetSameSite(http.SameSiteStrictMode)Spring import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletResponse;import org.springframework.core.env.Environment;@Autowiredprivate Environment env;// After receiving the authResult from scalekit.authenticateWithCode()String accessToken = authResult.getAccessToken();int expiresIn = authResult.getExpiresIn();String refreshToken = authResult.getRefreshToken();User user = authResult.getUser();// Store the refresh token in your databasedb.saveRefreshToken(user.getId(), refreshToken);// Set the access token as a secure cookieCookie cookie = new Cookie("accessToken", accessToken);cookie.setMaxAge((expiresIn - 60) * 1000); // Convert to milliseconds, subtract 60s buffercookie.setHttpOnly(true);cookie.setSecure("production".equals(env.getActiveProfiles()[0]));cookie.setPath("/");response.addCookie(cookie);Configure session settings from your Scalekit dashboard’s Session Configuration to control session lifetimes and security policies.
-
Verify the access token
Section titled “Verify the access token”Create middleware to protect your application routes. This middleware validates the access token on every request to secured endpoints.
middleware/auth.js async function verifyToken(req, res, next) {const { accessToken } = req.cookies;if (!accessToken) {return res.status(401).json({ error: 'Authentication required' });}try {// Validate the token using Scalekit SDKconst { user } = await scalekit.validateAccessToken(accessToken);req.user = user; // Attach user information to the requestnext();} catch (error) {// Token is expired or invalid - attempt to refreshreturn handleTokenRefresh(req, res, next);}}middleware/auth.py from flask import request, jsonifyfrom functools import wrapsdef verify_token(f):@wraps(f)def decorated_function(*args, **kwargs):access_token = request.cookies.get('accessToken')if not access_token:return jsonify({'error': 'Authentication required'}), 401try:# Validate the token using Scalekit SDKuser = scalekit.validate_access_token(access_token)request.user = user # Attach user information to the requestreturn f(*args, **kwargs)except Exception:# Token is expired or invalid - attempt to refreshreturn handle_token_refresh(f, *args, **kwargs)return decorated_functionmiddleware/auth.go import ("net/http""github.com/gin-gonic/gin")func VerifyToken() gin.HandlerFunc {return func(c *gin.Context) {accessToken, err := c.Cookie("accessToken")if err != nil || accessToken == "" {c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})c.Abort()return}// Validate the token using Scalekit SDKuser, err := scalekit.ValidateAccessToken(accessToken)if err != nil {// Token is expired or invalid - attempt to refreshhandleTokenRefresh(c)return}c.Set("user", user) // Attach user information to the contextc.Next()}}middleware/AuthInterceptor.java import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.Cookie;import org.springframework.web.servlet.HandlerInterceptor;@Componentpublic class AuthInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {String accessToken = getCookieValue(request, "accessToken");if (accessToken == null) {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.getWriter().write("{\"error\": \"Authentication required\"}");return false;}try {// Validate the token using Scalekit SDKUser user = scalekit.validateAccessToken(accessToken);request.setAttribute("user", user); // Attach user information to the requestreturn true;} catch (Exception e) {// Token is expired or invalid - attempt to refreshreturn handleTokenRefresh(request, response);}}private String getCookieValue(HttpServletRequest request, String cookieName) {Cookie[] cookies = request.getCookies();if (cookies != null) {for (Cookie cookie : cookies) {if (cookieName.equals(cookie.getName())) {return cookie.getValue();}}}return null;}} -
Refresh expired access tokens
Section titled “Refresh expired access tokens”When access tokens expire, use the refresh token to get new ones. This maintains user sessions without requiring re-authentication.
middleware/auth.js async function handleTokenRefresh(req, res, next) {// Get the user ID from the expired token or session storageconst userId = req.session?.userId || req.user?.id;if (!userId) {return res.status(401).json({ error: 'Authentication required' });}// Retrieve the stored refresh token from your databaseconst storedRefreshToken = await db.getRefreshToken(userId);if (!storedRefreshToken) {return res.status(401).json({ error: 'Session expired' });}try {// Get new tokens using the refresh tokenconst authResult = await scalekit.refreshToken(storedRefreshToken);const { accessToken, expiresIn, refreshToken: newRefreshToken } = authResult;// Update the stored refresh tokenawait db.saveRefreshToken(userId, newRefreshToken);// Set the new access token as a cookieres.cookie('accessToken', accessToken, {maxAge: (expiresIn - 60) * 1000,httpOnly: true,secure: process.env.NODE_ENV === 'production',sameSite: 'strict'});// Attach user information and continuereq.user = authResult.user;next();} catch (error) {// Refresh failed - user must sign in againres.clearCookie('accessToken');return res.status(401).json({ error: 'Session expired. Please sign in again.' });}}middleware/auth.py from flask import request, jsonify, make_responsedef handle_token_refresh(f, *args, **kwargs):# Get the user ID from the expired token or session storageuser_id = request.session.get('userId') if hasattr(request, 'session') else getattr(request, 'user', {}).get('id')if not user_id:return jsonify({'error': 'Authentication required'}), 401# Retrieve the stored refresh token from your databasestored_refresh_token = db.get_refresh_token(user_id)if not stored_refresh_token:return jsonify({'error': 'Session expired'}), 401try:# Get new tokens using the refresh tokenauth_result = scalekit.refresh_token(stored_refresh_token)access_token = auth_result.access_tokenexpires_in = auth_result.expires_innew_refresh_token = auth_result.refresh_token# Update the stored refresh tokendb.save_refresh_token(user_id, new_refresh_token)# Set the new access token as a cookieresponse = make_response(f(*args, **kwargs))response.set_cookie('accessToken',access_token,max_age=(expires_in - 60) * 1000,httponly=True,secure=os.environ.get('FLASK_ENV') == 'production',samesite='Strict')# Attach user information and continuerequest.user = auth_result.userreturn responseexcept Exception:# Refresh failed - user must sign in againresponse = make_response(jsonify({'error': 'Session expired. Please sign in again.'}), 401)response.set_cookie('accessToken', '', expires=0)return responsemiddleware/auth.go import ("net/http""github.com/gin-gonic/gin")func handleTokenRefresh(c *gin.Context) {// Get the user ID from the expired token or session storageuserID := getUserIDFromContext(c) // Helper function to get user IDif userID == "" {c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})c.Abort()return}// Retrieve the stored refresh token from your databasestoredRefreshToken, err := db.GetRefreshToken(userID)if err != nil || storedRefreshToken == "" {c.JSON(http.StatusUnauthorized, gin.H{"error": "Session expired"})c.Abort()return}// Get new tokens using the refresh tokenauthResult, err := scalekit.RefreshToken(storedRefreshToken)if err != nil {// Refresh failed - user must sign in againc.SetCookie("accessToken", "", -1, "/", "", false, true)c.JSON(http.StatusUnauthorized, gin.H{"error": "Session expired. Please sign in again."})c.Abort()return}// Update the stored refresh tokendb.SaveRefreshToken(userID, authResult.RefreshToken)// Set the new access token as a cookiec.SetCookie("accessToken",authResult.AccessToken,(authResult.ExpiresIn-60)*1000,"/","",os.Getenv("GIN_MODE") == "release",true,)// Attach user information and continuec.Set("user", authResult.User)c.Next()}middleware/AuthInterceptor.java import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.Cookie;private boolean handleTokenRefresh(HttpServletRequest request, HttpServletResponse response)throws Exception {// Get the user ID from the expired token or session storageString userId = getUserIdFromRequest(request); // Helper method to get user IDif (userId == null) {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.getWriter().write("{\"error\": \"Authentication required\"}");return false;}// Retrieve the stored refresh token from your databaseString storedRefreshToken = db.getRefreshToken(userId);if (storedRefreshToken == null) {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.getWriter().write("{\"error\": \"Session expired\"}");return false;}try {// Get new tokens using the refresh tokenAuthResult authResult = scalekit.refreshToken(storedRefreshToken);String accessToken = authResult.getAccessToken();int expiresIn = authResult.getExpiresIn();String newRefreshToken = authResult.getRefreshToken();// Update the stored refresh tokendb.saveRefreshToken(userId, newRefreshToken);// Set the new access token as a cookieCookie cookie = new Cookie("accessToken", accessToken);cookie.setMaxAge((expiresIn - 60) * 1000);cookie.setHttpOnly(true);cookie.setSecure("production".equals(env.getActiveProfiles()[0]));cookie.setPath("/");response.addCookie(cookie);// Attach user information and continuerequest.setAttribute("user", authResult.getUser());return true;} catch (Exception e) {// Refresh failed - user must sign in againCookie expiredCookie = new Cookie("accessToken", "");expiredCookie.setMaxAge(0);response.addCookie(expiredCookie);response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.getWriter().write("{\"error\": \"Session expired. Please sign in again.\"}");return false;}}Apply this middleware to protected routes:
app.js // Protect routes that require authenticationapp.get('/dashboard', verifyToken, (req, res) => {res.json({ message: `Welcome ${req.user.name}!` });});app.get('/api/profile', verifyToken, (req, res) => {res.json({ user: req.user });});app.py # Protect routes that require authentication@app.route('/dashboard')@verify_tokendef dashboard():return jsonify({'message': f'Welcome {request.user.name}!'})@app.route('/api/profile')@verify_tokendef profile():return jsonify({'user': request.user})main.go // Protect routes that require authenticationr.GET("/dashboard", VerifyToken(), func(c *gin.Context) {user, _ := c.Get("user")c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Welcome %s!", user.Name),})})r.GET("/api/profile", VerifyToken(), func(c *gin.Context) {user, _ := c.Get("user")c.JSON(http.StatusOK, gin.H{"user": user})})Controller.java // Protect routes that require authentication@GetMapping("/dashboard")public ResponseEntity<Map<String, String>> dashboard(HttpServletRequest request) {User user = (User) request.getAttribute("user");Map<String, String> response = new HashMap<>();response.put("message", "Welcome " + user.getName() + "!");return ResponseEntity.ok(response);}@GetMapping("/api/profile")public ResponseEntity<Map<String, Object>> profile(HttpServletRequest request) {User user = (User) request.getAttribute("user");Map<String, Object> response = new HashMap<>();response.put("user", user);return ResponseEntity.ok(response);}
Configure session settings
Section titled “Configure session settings”Control user session behavior from your Scalekit dashboard without changing your application code.
You can control how long users stay signed in and how often they need to log in again. In your Scalekit dashboard, the Session settings page lets you set these options:
-
Absolute session timeout: This is the maximum time a user can stay signed in, no matter what. After this time, they must log in again. For example, if you set it to 30 minutes, users will be logged out after 30 minutes, even if they are still using your app.
-
Idle session timeout: This is the time your app waits before logging out a user who is not active. If you turn this on, the session will end if the user does nothing for the set time. For example, if you set it to 10 minutes, and the user does not click or type for 10 minutes, they will be logged out.
-
Access token lifetime: This is how long an access token is valid. When it expires, your app needs to get a new token (using the refresh token) so the user can keep using the app without logging in again. For example, if you set it to 5 minutes, your app will need to refresh the token every 5 minutes.
Shorter timeouts provide better security, while longer timeouts reduce authentication interruptions.
What’s next?
Section titled “What’s next?”- Learn how to implement secure user logout to properly end user sessions.
- Explore session configuration options to customize security policies.
- Set up organization-level authentication policies for enterprise customers.