Skip to content

Manage user sessions

Store tokens safely with proper cookie security, validate on every request, and refresh with rotation to keep sessions secure

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.

This guide shows you how to store these tokens securely with encryption and proper cookie attributes, validate them on every request, and refresh them transparently in middleware to maintain seamless user sessions.

Review the session management sequence

User session management flow diagram showing how access tokens and refresh tokens work together

  1. After successful identity verification using any of the auth methods (Magic Link & OTP, social, enterprise SSO), your application receives session tokens(access and refresh tokens) towards the end of the login.

    {
    user: {
    email: "john.doe@example.com",
    emailVerified: true,
    givenName: "John",
    name: "John Doe",
    id: "usr_74599896446906854"
    },
    idToken: "eyJhbGciO..", // Decode for full user details
    accessToken: "eyJhbGciOi..",
    refreshToken: "rt_8f7d6e5c4b3a2d1e0f9g8h7i6j..",
    expiresIn: 299 // in seconds
    }

    Store each token based on its security requirements. For SPAs and mobile apps, consider storing access tokens in memory and sending via Authorization: Bearer headers to minimize CSRF exposure. For traditional web apps, use the cookie-based approach below:

    • Access Token: Store in a secure, HttpOnly cookie with proper Path scoping (e.g., /api) to prevent XSS attacks. This token has a short lifespan and provides access to protected resources.
    • Refresh Token: Store in a separate HttpOnly, Secure cookie with Path=/auth/refresh scoping. This limits the refresh token to only be sent to your refresh endpoint, reducing exposure. Rotate the token on each use to detect theft.
    • ID Token: Ensure it is stored in local storage or a cookie so that it remains accessible at runtime, which is necessary for logging the user out successfully.
    Express.js
    4 collapsed lines
    import cookieParser from 'cookie-parser';
    // Enable parsing of cookies from request headers
    app.use(cookieParser());
    // Extract authentication data from the successful authentication response
    const { accessToken, expiresIn, refreshToken, user } = authResult;
    // Encrypt tokens before storing to add an additional security layer
    const encryptedAccessToken = encrypt(accessToken);
    const encryptedRefreshToken = encrypt(refreshToken);
    // Store encrypted access token in HttpOnly cookie
    res.cookie('accessToken', encryptedAccessToken, {
    maxAge: (expiresIn - 60) * 1000, // Subtract 60s buffer for clock skew (milliseconds)
    httpOnly: true, // Prevents JavaScript access to mitigate XSS attacks
    secure: process.env.NODE_ENV === 'production', // HTTPS-only in production
    sameSite: 'strict' // Prevents CSRF attacks
    });
    // Store encrypted refresh token in separate HttpOnly cookie
    res.cookie('refreshToken', encryptedRefreshToken, {
    httpOnly: true, // Prevents JavaScript access to mitigate XSS attacks
    secure: process.env.NODE_ENV === 'production', // HTTPS-only in production
    sameSite: 'strict' // Prevents CSRF attacks
    });
  2. Check the access token before handling requests

    Section titled “Check the access token before handling requests”

    Validate every request for a valid access token in your application. Create middleware to protect your application routes. This middleware validates the access token on every request to secured endpoints. For APIs, consider reading from Authorization: Bearer headers instead of cookies to minimize CSRF risk.

    Here’s an example middle ware method validating access token and refreshing the token if it’s expired for every request.

    middleware/auth.js
    async function verifyToken(req, res, next) {
    // Extract encrypted tokens from request cookies
    const { accessToken, refreshToken } = req.cookies;
    if (!accessToken) {
    return res.status(401).json({ error: 'Authentication required' });
    }
    try {
    // Decrypt the access token before validation
    const decryptedAccessToken = decrypt(accessToken);
    // Verify token validity using Scalekit's validation method
    const isValid = await scalekit.validateAccessToken(decryptedAccessToken);
    if (!isValid && refreshToken) {
    // Token expired - refresh it transparently
    const decryptedRefreshToken = decrypt(refreshToken);
    const authResult = await scalekit.refreshAccessToken(decryptedRefreshToken);
    // Encrypt and store new tokens
    res.cookie('accessToken', encrypt(authResult.accessToken), {
    maxAge: (authResult.expiresIn - 60) * 1000,
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict'
    });
    res.cookie('refreshToken', encrypt(authResult.refreshToken), {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict'
    });
    return next();
    }
    if (!isValid) {
    return res.status(401).json({ error: 'Session expired. Please sign in again.' });
    }
    // Token is valid, proceed to the next middleware or route handler
    next();
    } catch (error) {
    return res.status(401).json({ error: 'Authentication failed' });
    }
    }
  3. Manage user session behavior directly from your Scalekit dashboard without modifying application code. Configure session durations and authentication frequency to balance security and user experience for your application.

    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.

  4. Beyond client-side session management, Scalekit provides powerful APIs to manage user sessions remotely from your backend application. This enables you to build features like active session management in user account settings, security incident response, or administrative session control.

    These APIs are particularly useful for:

    • Displaying all active sessions in user account settings
    • Allowing users to revoke specific sessions from unfamiliar devices
    • Security incident response and suspicious session termination
    Session Management SDK
    // Get details for a specific session
    const sessionDetails = await scalekit.session.getSession('ses_1234567890123456');
    // List all sessions for a user with optional filtering
    const userSessions = await scalekit.session.getUserSessions('usr_1234567890123456', {
    pageSize: 10,
    filter: {
    status: ['ACTIVE'], // Filter for active sessions only
    startTime: new Date('2025-01-01T00:00:00Z'),
    endTime: new Date('2025-12-31T23:59:59Z')
    }
    });
    // Revoke a specific session (useful for "Sign out this device" functionality)
    const revokedSession = await scalekit.session.revokeSession('ses_1234567890123456');
    // Revoke all sessions for a user (useful for "Sign out all devices" functionality)
    const revokedSessions = await scalekit.session.revokeAllUserSessions('usr_1234567890123456');
    console.log(`Revoked sessions for user`);

Your application continuously validates the access token for each incoming request. When the token is valid, the user’s session remains active. If the access token expires, your middleware transparently refreshes it using the stored refresh token—users never notice this happening. If the refresh token itself expires or becomes invalid, users are prompted to sign in again.