Blog

Session management best practices

If you think you’re done when you authenticate a user, think again. Proper session management can make or break your app, both security and UX-wise. We gathered some industry best practices to help you get started.


Secure session management is crucial for keeping web applications and their users safe. Otherwise, you are exposed to security vulnerabilities, like unauthorized access, data breaches, and account hijacking. To avoid such risks and maintain a secure environment, web developers and system administrators should be careful and avoid common pitfalls.

In this article, we will explore some common best practices for session management and see how you can implement each using Node or Python.

Sessions 101

A user session refers to the period of time during which a user interacts with your app. It starts when the user logs in or opens the application and ends when they log out, close the app, or become inactive for a certain amount of time.

User sessions help track user activity, improve the user experience, and enable features like personalized content, user authentication, or maintaining shopping carts. Each session receives a unique identifier, generally a session ID or token, which is often stored in a cookie on the user’s browser.

The session management flow looks like this:

  1. The user signs in.
  2. The app creates a unique session identifier or token for the user. This identifier is stored in a cookie and contains information about the user and the session. The session expires after a certain amount of time.
  3. The user can keep using the app without having to authenticate again until they log out or become inactive for a certain amount of time (i.e., the session expires).

Sounds simple enough, but there are many pitfalls ahead. Let’s see some best practices you should remember when implementing session management.

Use secure session IDs

One of the foundational elements of session management is the session identifier (ID), which uniquely identifies a user's session. The session ID should be:

  • Random and unpredictable: Using predictable or sequential session IDs makes it easier for attackers to guess or brute-force session identifiers, compromising the session's security. Always generate session IDs using a secure, random algorithm with sufficient entropy (e.g., 128-bit or 256-bit randomness).
  • Long and unique: A shorter or overly simplistic session ID increases the risk of a collision or prediction. Ensuring each session ID is long enough minimizes this risk.

How to generate a secure session ID with Python:

  
import secrets
import string

def generate_session_id(length=32):
    characters = string.ascii_letters + string.digits
    session_id = ''.join(secrets.choice(characters) for _ in range(length))
    return session_id
  
  • The secrets module is explicitly designed for cryptographic use and produces a very strong random number suitable for security-sensitive applications like session IDs or passwords.
  • You can easily customize the character set (letters, digits, symbols) used in the session ID by modifying the characters string.

How to generate a secure session ID with Node:

	
const crypto = require('crypto');

function generateSessionId(length = 32) {
    // Generate a secure random session ID (in hexadecimal format)
    return crypto.randomBytes(length).toString('hex');
}

// Example usage
const sessionId = generateSessionId();
console.log(`Generated session ID: ${sessionId}`);
	
  • crypto.randomBytes(length): This function generates a specified number of cryptographically secure random bytes.
  • toString('hex'): Converts the random bytes into a hexadecimal string. The default is 32 bytes, which gives you a 64-character hexadecimal string.
  • Length: The default length is 32 bytes, but you can change it if you want a longer or shorter session ID.

Use secure session cookies

Session cookies are the most common way to store session IDs on the client side. But you must be careful when sending them from one service to another. To prevent interception or manipulation of session cookies:

  • Set the Secure flag: This ensures the cookie is only sent over secure HTTPS connections, preventing interception over unencrypted HTTP.
  • Set the HttpOnly flag: This prevents client-side JavaScript from accessing the session cookie, reducing the risk of XSS (Cross-Site Scripting) attacks. HTTPOnly cookies can only be accessed by the backend server.
  • Use the SameSite attribute: This prevents cross-site request forgery (CSRF) attacks by ensuring the cookie is only sent to the domain that set it. You have the following options:
    • Strict: Cookies are only sent in same-site requests, preventing cross-site request forgery (CSRF).
    • Lax: Cookies are sent in same-site and some cross-site requests, offering a balance between security and user experience.
    • None: Cookies are sent with all requests, requiring additional security measures like Secure and HttpOnly.
  • Expires/Max-Age: These attributes define how long a cookie will be valid (unless explicitly destroyed due to logout). When set, the cookie will remain persistently stored on the browser until the specified expiration time or max-age.

How to create a secure cookie with Python (Flask):

  
from flask import Flask, make_response

def set_session():
    # Generate a secure session ID using the function of the previous example
    session_id = generate_session_id()

    # Create a response object
    response = make_response("Session cookie set.")

    # Set the session_id cookie with Secure, HttpOnly, and SameSite attributes
    response.set_cookie(
        'session_id', 
        session_id, 
        httponly=True,          # Ensures the cookie is not accessible via JavaScript
        secure=True,            # Ensures the cookie is only sent over HTTPS
        samesite='Strict',      # Prevents the cookie from being sent with cross-site requests
        max_age=3600            # The cookie expires in 1 hour
    )

    return response
  

How to create a secure cookie with Node (Express):

	
const express = require('express');

const app = express();

// Route to set the secure cookie
app.get('/set-session', (req, res) => {
    // Generate a secure session ID using the function of the previous example
    const sessionId = generateSessionId();

    // Set the cookie with the Secure, HttpOnly, and SameSite attributes
    res.cookie('session_id', sessionId, {
        httpOnly: true,    // Ensures the cookie is not accessible via JavaScript
        secure: true,      // Ensures the cookie is only sent over HTTPS
        sameSite: 'Strict', // Prevents the cookie from being sent with cross-site requests
        maxAge: 3600000     // The cookie expires in 1 hour
    });

    res.send('Session cookie set.');
});
	

This is what that response will look like:

	
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session_id=<generated_session_id>; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
Content-Length: 34
Date: Mon, 19 Feb 2025 12:00:00 GMT
Connection: keep-alive

Session cookie set.
	

Session expiry and timeouts

To mitigate the risk of session hijacking or unauthorized access, it’s important to implement appropriate session expiration and timeout mechanisms:

  • Set short expiration times: Sessions should not last indefinitely. Sessions should expire after a reasonable period of inactivity (e.g., 15-30 minutes). If a user is inactive for this period, they should be logged out automatically.
  • Implement sliding expiry: If the user is active during the session, extend the expiration time. This helps to prevent session expiration during legitimate use while reducing risks for inactive users.
  • Log out after a fixed period: Even if a session is active, it should eventually expire after a defined period (e.g., 24 hours), requiring the user to log in again.

There is no silver bullet here, it depends on whether the app is high or low risk. Banks will log you out after 5 minutes of inactivity while other low-risk apps will leave you logged in for hours (or days). You should evaluate your own needs while trying to balance user experience and security.

The OWASP Session Management Cheat Sheet advises: “Both the idle and absolute timeout values are highly dependent on how critical the web application and its data are. Common idle timeouts ranges are 2-5 minutes for high-value applications and 15-30 minutes for low risk applications. Absolute timeouts depend on how long a user usually uses the application. If the application is intended to be used by an office worker for a full day, an appropriate absolute timeout range could be between 4 and 8 hours.”

Implement HTTPS

Always encrypt session traffic with HTTPS. This ensures data in transit (including session IDs) is protected from man-in-the-middle attacks and eavesdroppers.

Regenerate sessions upon login and logout

To prevent session fixation attacks, session IDs should be regenerated after the user successfully logs in or logs out:

  • Regenerate session ID after login: When a user logs in, their session ID should be regenerated to prevent attackers from hijacking or predicting a session ID.
  • Regenerate session ID after privilege elevation: If a user changes their privileges (e.g., admin access), regenerate their session ID to protect against privilege escalation attacks.

Destroy session on logout

Ensure that a session is fully terminated when a user logs out. This includes clearing session data and invalidating the session ID.Example using Python and Flask:

  
from flask import Flask, make_response
app = Flask(__name__)

# Route to clear the session and log out
@app.route('/logout')
def logout():
    # Clear the session cookie by setting it to expire immediately
    response = make_response("Logged out! Session cookie has been cleared.")
    
    # Clear the session_id cookie
    response.delete_cookie(
        'session_id', 
        httponly=True,           # Make sure the cookie is securely deleted
        secure=True,             # Secure flag must match
        samesite='Strict'        # SameSite must match the initial setting
    )
    
    return response
  

Example using Node and Express:

	
// Route to clear the session and log out
app.get('/logout', (req, res) => {
    // Clear the session cookie by setting it to expire immediately
    res.clearCookie('session_id', {
        httpOnly: true,
        secure: true,      // Make sure to match the secure flag if using HTTPS
        sameSite: 'Strict' // SameSite should match the previous setting
    });

    res.send('Logged out.');
});
	
  • This route clears the session cookie by calling res.clearCookie(). It tells the browser to delete the cookie by setting it to expire immediately.
  • It also uses the same cookie attributes (httpOnly, secure, and sameSite) to ensure the cookie is properly cleared.
  • Once the cookie is cleared, the user is logged out.

Use secure session storage

Avoid storing sensitive session data like passwords, personal information, or tokens directly within the session. Instead:

  • Store minimal data: Store only a session ID or reference to the session data (e.g., user ID, session state) and fetch other information from the database or another secure source.
  • Use server-side session storage: For higher security, store session data on the server-side, especially for sensitive information, and avoid relying solely on client-side storage (like cookies or local storage).

Monitor and audit sessions

Continuously monitor and log session activity to detect suspicious or unusual patterns:

  • Track login locations: Log the IP address and user-agent (browser/device information) for each session to detect anomalies such as logins from different geographical locations in a short period.
  • Alert on abnormal behavior: Set up alerts for suspicious activities such as multiple failed login attempts or rapid session creation and destruction.
  • Conduct regular audits: Periodically audit session management practices and ensure compliance with security policies.

Tools like WorkOS Radar can help detect bots, impossible travel, brute-force attacks, and more and either automatically block them or alert the security team.

Persist sessions across servers

If your application uses a load-balancing system, a user's requests might be distributed across different servers, which could cause issues like losing session data (e.g., user preferences or shopping cart items). Ensure that session management works seamlessly across different servers using one of the following options:

  • Sticky sessions: The load balancer routes the user's requests to the same server, often by tracking the session through a cookie or session ID. This helps maintain continuity and ensures a smoother user experience.
  • Shared session stores: Use shared session stores, like Redis or Memcached, so users can be routed to any server without losing their session data. This allows for better load balancing and scalability since requests can be routed to any server.
  • Use JSON Web Tokens (JWT): JWTs are designed to be stateless. This means that the session data is stored in the token itself and doesn't require a centralized session store like Redis. Each JWT contains all the information the server needs, such as user authentication data, permissions, and other claims, and it is signed so it can be trusted without needing to reference a database. It should be noted that you might still have to use JWT and a shared session store like Redis. In this case, Redis can be used to store extra session-related data (like shopping cart items), while JWTs are used for authentication.

Validate tokens

If you use JWTs, you must always validate them before use. This includes:

  • Parse the JWT to extract the header, payload, and signature.
  • Verify the signature using the secret key or public key.
  • Check the expiration time (exp) and the not-before time (nbf) claims to ensure the JWT is valid.
  • Verify the issuer (iss) claim to ensure the JWT was issued by a trusted party.
  • Verify the audience (aud) claim to ensure the JWT is intended for the correct recipient.

Use Multi-Factor Authentication (MFA)

While not directly related to session management, multi-factor authentication (MFA) strengthens session security significantly by adding an extra layer of verification.

When users want to perform sensitive actions like changing their password or accessing sensitive information, ask them to provide a second form of authentication (e.g., SMS code, authenticator app). If your app is high-risk, consider requiring MFA as part of the initial login process.

UX tip: Implement graceful session expiry

Keeping users safe online is a constant battle between security and usability. Use these tips to improve the user experience with regard to session management.

Instead of abruptly logging out users, inform them in advance that their session is about to expire and give them an option to extend it (similar to how airlines warn you when you are in the process of booking tickets and have been idle for a while). This ensures users don’t lose their progress unexpectedly.

Conclusion

Implementing these session management best practices is key to building secure, reliable web applications. By generating robust session IDs, mandating HTTPS, configuring cookies correctly, enforcing timeouts, rotating session IDs, and diligently monitoring user sessions, you can maintain a safe environment and deliver a seamless user experience.

In this article

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.