Database Transactions in PHP: Begin, Commit & Rollback

13 min read 2,453 words
Database Transactions in PHP: Making Sure Related Changes Either All Succeed or All Fail featured image

What PHP Sessions Are and Why They Matter

PHP sessions provide a way to store user information across multiple page requests. When a visitor lands on your site, PHP creates a unique session ID that identifies their browser throughout their visit. This session ID links to data stored on the server, allowing your application to remember login state, shopping cart contents, user preferences, and other temporary information.

The session ID itself travels between the browser and server with each request, typically transmitted via a cookie named PHPSESSID. The actual session data lives on the server, not in the browser. This separation is important for security, but it only works well when the session system is configured properly.

Poor session configuration is a common entry point for attackers. Session fixation, session hijacking, and unauthorised access can result from missing security flags, predictable session IDs, or inadequate timeout settings. Understanding how sessions work and how to configure them securely is a practical skill for anyone building or maintaining PHP applications.

How PHP Session Configuration Works

PHP session behaviour is controlled through the php.ini configuration file or at runtime using the ini_set() function. The runtime approach is useful when you do not have access to the server configuration, such as on shared hosting.

You can check your current session settings at any time using ini_get_all() or by viewing the specific value you need:

echo ini_get('session.cookie_httponly');
echo ini_get('session.cookie_secure');
echo ini_get('session.use_strict_mode');

These settings control how cookies behave, how strictly PHP enforces session security, and how long sessions remain valid. Each setting affects a different aspect of the session lifecycle.

The Session Cookie Settings That Matter Most

The session cookie is where most security vulnerabilities appear. When a session cookie lacks proper flags, it becomes easier for attackers to steal or manipulate.

HTTPOnly flag: Prevents JavaScript from accessing the session cookie. This blocks cross-site scripting (XSS) attacks from reading the cookie value. Without this flag, an XSS vulnerability could expose the session ID to attackers.

ini_set('session.cookie_httponly', 1);

Secure flag: Ensures the cookie is only sent over HTTPS connections. On sites running SSL, this prevents the session ID from being transmitted in plain text when a user connects over HTTP. This matters particularly on sites where some pages may load over insecure connections.

ini_set('session.cookie_secure', 1);

SameSite attribute: Controls whether the cookie is sent with cross-site requests. The Strict value prevents the cookie from being sent on cross-site navigation, which offers strong protection against cross-site request forgery (CSRF) but may affect legitimate cross-site flows. The Lax value allows the cookie on top-level navigation but blocks it for subrequests like images or iframes. The None value permits cross-site sending but requires the Secure flag.

ini_set('session.cookie_samesite', 'Lax');

Setting the SameSite attribute directly on the session cookie requires PHP 7.3 or later. On older versions, you may need to set the header manually:

session_start([
    'cookie_lifetime' => 0,
    'cookie_samesite' => 'Lax',
]);

Session Regeneration and Why It Matters

Session fixation is an attack where an attacker sets or learns a user's session ID before the user logs in. After the user authenticates, the attacker uses that same session ID to hijack the authenticated session.

The defence is to regenerate the session ID immediately after any authentication event, such as login. PHP provides session_regenerate_id() for this purpose. When called, it creates a new session ID and invalidates the old one. The old session data is preserved during the regeneration so the user does not lose their context.

session_regenerate_id(true);

The boolean argument, when set to true, deletes the old session file immediately. Without it, the old session remains on disk until the next garbage collection cycle, which creates a brief window where an attacker could still use the old ID.

Regenerating the session ID after login is one of the most effective ways to prevent session fixation attacks. It should be a standard part of any authentication flow. Beyond login, consider regenerating after privilege escalation, such as when an admin accesses sensitive areas of your application.

For a more detailed walkthrough of session handling during login, including secure state management patterns, the article on understanding PHP sessions and login state securely covers practical implementation approaches.

Session Storage: Files versus Databases

By default, PHP stores session data in files on the server filesystem, typically in a directory like /var/lib/php/sessions. This works for single-server setups but creates problems when your application runs across multiple servers.

File-based sessions on shared hosting can also be accessed by other scripts running under the same user, depending on server configuration. While PHP isolates session files by user, the filesystem remains a shared resource, and misconfigured servers can expose session data.

Database session storage solves these problems by keeping session data in your application database. This approach gives you complete control over session data, enables horizontal scaling across multiple servers, and makes it straightforward to query, audit, or clear sessions programmatically.

Implementing custom session handling requires setting a custom save handler. PHP provides the SessionHandlerInterface which you can implement to store sessions in any backend:

class DatabaseSessionHandler implements SessionHandlerInterface {
    private $db;

    public function __construct(PDO $db) {
        $this->db = $db;
    }

    public function open($savePath, $sessionName): bool {
        return true;
    }

    public function read($sessionId): string {
        $stmt = $this->db->prepare(
            "SELECT data FROM sessions WHERE id = ?"
        );
        $stmt->execute([$sessionId]);
        $result = $stmt->fetchColumn();
        return $result ?: '';
    }

    public function write($sessionId, $data): bool {
        $stmt = $this->db->prepare(
            "REPLACE INTO sessions (id, data, updated_at) VALUES (?, ?, NOW())"
        );
        return $stmt->execute([$sessionId, $data]);
    }

    public function destroy($sessionId): bool {
        $stmt = $this->db->prepare("DELETE FROM sessions WHERE id = ?");
        return $stmt->execute([$sessionId]);
    }

    public function gc($maxlifetime): bool {
        $stmt = $this->db->prepare(
            "DELETE FROM sessions WHERE updated_at < DATE_SUB(NOW(), INTERVAL ? SECOND)"
        );
        return $stmt->execute([$maxlifetime]);
    }

    // Additional interface methods omitted for brevity
}

Register this handler before calling session_start():

$handler = new DatabaseSessionHandler($pdo);
session_set_save_handler($handler);
session_start();

When using database sessions, consider creating an index on the session ID column and the updated_at timestamp to keep session reads fast as the table grows. If your application deals with high traffic, a separate database optimized for write-heavy workloads may be worth considering. The article on database indexing strategy explains how to approach index design for performance-critical tables.

Session Timeout and Inactivity Expiration

Sessions that persist indefinitely create a security risk. If a user leaves a device unattended while logged in, an attacker with physical or remote access could continue using that session indefinitely.

PHP provides two approaches to session expiration. The first is session.gc_maxlifetime, which controls how long session data remains on disk before the garbage collector may delete it. This is a maximum lifetime, not a guaranteed expiration, because garbage collection only runs probabilistically based on the session.gc_probability and session.gc_divisor settings.

ini_set('session.gc_maxlifetime', 1800); // 30 minutes

The second approach, which is more reliable, is to track last activity time in the session data itself and validate it on each request. This gives you precise control and does not depend on garbage collection timing:

session_start();

$inactivityTimeout = 1800; // 30 seconds in seconds

if (isset($_SESSION['last_activity']) && 
    (time() - $_SESSION['last_activity']) > $inactivityTimeout) {
    
    session_unset();
    session_destroy();
    session_start();
}

$_SESSION['last_activity'] = time();

For sensitive applications, consider combining session timeout with absolute maximum session lifetime. A session that has been active for more than a few hours, even if not idle, should be invalidated and require re-authentication. This limits the window available to attackers who may have obtained a session ID through less obvious means.

Strict Session Mode

PHP 5.4 introduced session.use_strict_mode. When enabled, PHP refuses to accept a session ID that does not exist in the session storage. This prevents session fixation attacks where an attacker sets a session ID in advance and tricks a user into using it.

ini_set('session.use_strict_mode', 1);

Without this setting, PHP will create a new session using any ID provided by the user, even if that ID was crafted by an attacker. With strict mode enabled, only IDs generated by PHP itself are accepted after the session is first created. This setting should be enabled on all production environments.

Regenerating Session IDs Periodically

Beyond regenerating the session ID after login, periodically regenerating the ID during a session limits the damage an attacker can do if they obtain a valid session ID. Even if an attacker captures a session ID, it becomes useless after the next regeneration.

Regenerate the session ID at a reasonable interval, such as every few hundred requests or every 15 to 30 minutes of active use:

if (!isset($_SESSION['regenerated_time'])) {
    $_SESSION['regenerated_time'] = time();
} elseif (time() - $_SESSION['regenerated_time'] > 1800) {
    session_regenerate_id(true);
    $_SESSION['regenerated_time'] = time();
}

This approach balances security against the overhead of regenerating session IDs on every request, which can interfere with concurrent tab usage or AJAX-heavy applications.

Logging Users Out Securely

Logging a user out should destroy their session completely. This means unsetting session variables, destroying the session cookie, and deleting the session data from storage.

$_SESSION = [];

if (ini_get('session.use_cookies')) {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params['path'], $params['domain'],
        $params['secure'], $params['httponly']
    );
}

session_destroy();

The manual clearing of session variables and cookies is necessary because session_destroy() does not unset the session cookie in all configurations. It also does not clear the $_SESSION array in the current script execution, which is why it is set to an empty array explicitly.

Common Session Security Mistakes

Several configuration errors appear repeatedly in PHP applications. Identifying and fixing these issues significantly improves session security.

  • Missing HTTPOnly flag: Without it, JavaScript can read the session cookie if your application has an XSS vulnerability. Always enable HTTPOnly on session cookies.
  • Missing Secure flag: On HTTPS sites, failing to set the Secure flag allows the session cookie to travel over unencrypted connections, exposing it to network-level interception.
  • Not regenerating session ID after login: This leaves your application vulnerable to session fixation attacks. Regenerate immediately after authentication succeeds.
  • Using default session cookie names: The default PHPSESSID cookie name signals to attackers that your application uses PHP sessions. Custom names add a small layer of obscurity.
  • Session IDs in URLs: Allowing session IDs in URLs exposes them in server logs, browser history, and referrer headers. Disable session.use_only_cookies to prevent this.
  • Disabled strict mode: Running with session.use_strict_mode disabled allows attackers to initialise sessions with arbitrary IDs.
  • Excessive session lifetime: Sessions that never expire remain valid indefinitely, increasing the window for session theft.

A Practical Session Configuration for Production

A hardened session configuration combines several settings. This example sets recommended values at the start of your application, before calling session_start():

ini_set('session.use_strict_mode', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Lax');
ini_set('session.gc_maxlifetime', 1800);
ini_set('session.cookie_lifetime', 0);

session_name('MYAPP_SESSION');

session_start();

$idleTimeout = 1800;
if (isset($_SESSION['last_activity']) && 
    (time() - $_SESSION['last_activity']) > $idleTimeout) {
    session_unset();
    session_destroy();
    session_start();
}
$_SESSION['last_activity'] = time();

This configuration enforces strict mode, restricts cookies to HTTP-only and HTTPS, sets a SameSite attribute, uses a custom session name, and implements activity-based timeout. Adjust the idleTimeout value based on your application requirements and user expectations.

When Session Configuration Needs a Review

Session security should be reviewed when you deploy new code, change hosting environments, update PHP versions, or notice unusual account activity. Shared hosting environments are particularly worth reviewing because other scripts on the same server may affect session storage or cookie behaviour.

If you are migrating between servers or updating PHP, session handling behaviour may change. Testing your authentication flow thoroughly after any infrastructure change helps catch configuration drift before it becomes a security issue.

For applications that handle sensitive data, consider a periodic security review of session handling as part of your maintenance schedule. This review should include checking that configuration settings match your intended policy, that session data is being stored securely, and that timeout and regeneration logic is working as expected.

If you need help reviewing your current session configuration or hardening an existing PHP application, you can get in touch with details of your setup, the PHP version you are running, and the hosting environment you use.

Frequently Asked Questions

What is the difference between session_regenerate_id() and session_regenerate_id(true)?
The session_regenerate_id() call without the true argument marks the old session for deletion but keeps it accessible until garbage collection runs. With true, PHP deletes the old session file immediately. Using true is recommended in security-sensitive contexts because it closes the window during which an attacker could use a stolen session ID.
Should I store sessions in a database or use the default file storage?
File-based storage works for simple deployments on a single server with well-configured hosting. Database storage is preferable when you run multiple application servers, need to query or audit sessions programmatically, or operate in an environment where you cannot trust the filesystem isolation between tenants. The trade-off is added complexity in implementation and a small amount of database overhead per request.
How long should a session last?
There is no single correct answer. Sessions that expire too quickly frustrate users. Sessions that persist too long increase security risk. A common practical approach is to set a reasonable inactivity timeout, such as 15 to 30 minutes, combined with periodic regeneration during active use. Sensitive applications may also enforce an absolute maximum lifetime, requiring re-authentication after several hours regardless of activity.
Does setting session.cookie_secure to 1 break my site on HTTP?
Yes, the Secure flag instructs browsers to only send the cookie over HTTPS. If any part of your site loads over HTTP, the session cookie will not be sent, and users will appear logged out. This is one reason to ensure your entire site runs over HTTPS. Modern hosting makes SSL certificates straightforward to configure, and services like Let's Encrypt make the process free. If you need to serve any pages over HTTP, those pages should not require session authentication.
Can I set session security options in .htaccess instead of in PHP code?
Yes, if you are running PHP as an Apache module, you can set session configuration directives in .htaccess using the php_value directive. This is useful when you do not have access to php.ini or prefer central configuration over runtime settings. For example: