Cross Domain Session In PHP – A Simple Example

Welcome to a tutorial on how to support cross-domain sessions in PHP. Yes, it is technically possible to share a single session across multiple domains, but it is quite a challenging task.

To support cross-domain sessions in PHP, we need to:

  • Save the sessions into a shared common database.
  • Set a single domain to facilitate the shared session. I.E. Forward all logins to this domain.

That covers the basic idea, read on for the details!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Notes Server Setup Shared Session
Useful Bits & Links The End

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

EXAMPLE CODE DOWNLOAD

Click here to download all the example source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

QUICK NOTES

If you spot a bug, please feel free to comment below. I try to answer questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

 

SERVER SETUP & OVERVIEW

All right, let us now start with the server setup and an overview of the dummy example.

 

EXAMPLE OVERVIEW

For this example, we are going to assume a 2 domains setup.

  • http://site-a.com Is the “main website” that we will use for the user login.
  • http://site-b.com Is the “sub-website” or “sister site”.
  • Both of them have access to a shared sessions database.

 

SIMULATING MULTIPLE DOMAINS ON LOCALHOST

There’s no need to register multiple domains and sub-domains to test things out. Here’s a trick that I often use to simulate multiple domains on localhost – Edit the host file, here’s how it’s done on a Windows desktop.

C:\Windows\System32\drivers\etc\hosts
127.0.0.1 site-a.com
127.0.0.1 site-b.com
::1 site-a.com
::1 site-b.com

Linux and Mac users, your host file should be located in /etc/hosts. But do your own research, it may be different for the different distros and versions. Next, add the virtual hosts.

httpd.conf
<VirtualHost *:80>
  DocumentRoot "D:/http/"
  ServerName localhost
</VirtualHost>
<VirtualHost *:80>
  DocumentRoot "D:/http/main"
  ServerName site-a.com
</VirtualHost>
<VirtualHost *:80>
  DocumentRoot "D:/http/sub"
  ServerName site-b.com
</VirtualHost>

 

 

PHP SHARED SESSION

Now that we have the virtual hosts in place, it’s time to deal with the cross-domain sessions themselves.

 

1) DATABASE SESSIONS TABLE

database.sql
CREATE TABLE `sessions` (
  `id` varchar(32) NOT NULL,
  `access` int(10) UNSIGNED DEFAULT NULL,
  `data` text DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE `sessions`
  ADD PRIMARY KEY (`id`);

First, we are going to need a database table to store the sessions. This should be pretty self-explanatory:

  • id The session ID.
  • access Last accessed timestamp.
  • data Session data.

 

2) DATABASE SESSIONS CLASS

LIB-session.php
<?php
// (A) DATABASE SESSION CLASS
class Session {
  // (A1) PROPERTIES
  public $pdo = null;
  public $stmt = null;
  public $error = "";
  public $lastID = null;

  // (A2) INIT - DATABASE SESSION
  function __construct () {
    session_set_save_handler(
      [$this, "open"],
      [$this, "close"],
      [$this, "read"],
      [$this, "write"],
      [$this, "destroy"],
      [$this, "gc"]
    );
    session_start();
  }

  // (A3) EXECUTE SQL QUERY
  function exec ($sql, $data=null) {
    try {
      $this->stmt = $this->pdo->prepare($sql);
      $this->stmt->execute($data);
      $this->lastID = $this->pdo->lastInsertId();
      return true;
    } catch (Exception $ex) {
      $this->error = $ex->getMessage();
      return false;
    }
  }

  // (A4) SQL FETCH
  function fetch ($sql, $data=null) {
    if (!$this->exec($sql, $data)) { return false; }
    return $this->stmt->fetch();
  }

  // (A5) SESSION OPEN - CONNECT TO DATABASE
  function open ($savePath, $sessionName) {
    try {
      $this->pdo = new PDO(
        "mysql:host=". SESS_DB_HOST .";charset=". SESS_DB_CHARSET .";dbname=". SESS_DB_NAME,
        SESS_DB_USER, SESS_DB_PASS, [
          PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
          PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
          PDO::ATTR_EMULATE_PREPARES => false
        ]
      );
      return true;
    } catch (Exception $ex) { exit ($ex->getMessage()); }
  }

  // (A6) SESSION CLOSE - CLOSE DATABASE
  function close () {
    if ($this->stmt !== null) { $this->stmt = null; }
    if ($this->pdo !== null) { $this->pdo = null; }
    return true;
  }

  // (A7) SESSION READ
  function read ($id) {
    $result = $this->fetch("SELECT * FROM `sessions` WHERE `id`=?", [$id]);
    return is_array($result) ? $result['data'] : "" ;
  }

  // (A8) SESSION WRITE
  function write ($id, $data) {
    return $this->exec(
      "REPLACE INTO `sessions` VALUES (?, ?, ?)",
      [$id, time(), $data]
    );
  }

  // (A9) SESSION DESTROY
  function destroy ($id) {
    return $this->exec("DELETE FROM `sessions` WHERE `id`=?", [$id]);
  } 

  // (A10) GARBAGE COLLECTOR
  function gc ($max) {
    return $this->exec("DELETE FROM `sessions` WHERE `access` < ?", [(time() - $max)]);
  }
}

// (B) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define("SESS_DB_HOST", "localhost");
define("SESS_DB_CHARSET", "utf8");
define("SESS_DB_NAME", "test");
define("SESS_DB_USER", "root");
define("SESS_DB_PASS", "");

// (C) START!
$_SESS = new Session();

Not going to explain this library line-by-line, but we basically use session_set_save_handler() to set our own custom PDO database session handlers.

 

 

3) SITE A – DUMMY LOGIN PAGE

main/index.php
<?php
// (A) START SESSION
require "LIB-session.php";
 
// (B) DO YOUR OWN LOGIN PROCESS
// FOR DEMO, WE ASSUME "JON DOE"
if (!isset($_SESSION['user'])) {
  $_SESSION['user'] = [
    "name" => "Jon Doe",
    "email" => "jon@doe.com"
  ];
}
 
// (C) FORWARD
if (isset($_POST['forward'])) {
  header('Location: http://site-b.com/?sess='.session_id());
  exit();
}
 
// (D) SHOW HTML ?>
<!DOCTYPE html>
<html>
  <head>
    <title>Main Website</title>
  </head>
  <body>
    <h1>Welcome To Main Website</h1>
    <?php print_r($_SESSION); ?>
  </body>
</html>

With the database sessions in place, the next step is to deal with the login on the “main site”.

  1. Captain Obvious. Start the database session.
  2. Lazy example here… Do your own proper login checks.
  3. This is for redirecting back to the “sub-site” after a successful login. Take note of how it passes the session-id ?sess=session_id() over.
  4. Just some dummy HTML.

That’s all. Access http://site-a.com and see how the session registers into the database.

 

 

4) SITE B – REDIRECTION MECHANICS

sub/index.php
<?php
// (A) SESSION START
require "LIB-session.php";
 
// (B) OVERRIDE SESSION
if (isset($_GET['sess'])) {
  session_destroy();
  session_id($_GET['sess']);
  session_start();
}
 
// (C) SHOW HTML ?>
<!DOCTYPE html>
<html>
  <head>
    <title>Sub Website</title>
    <!-- (D) REMOVE TRAILING SESSION ID FOR SECURITY -->
    <?php if (isset($_GET['sess'])) { ?>
    <script>location.replace("http://site-b.com");</script>
    <?php } ?>
  </head>
  <body>
    <h1>Welcome To Site Shop</h1>
    <?php
    // (E) SIGNED IN - SHOW CURRENT SESSION
    if (isset($_SESSION['user'])) { print_r($_SESSION); }
 
    // (F) NOT SIGNED IN - SHOW LOGIN FORM
    else { ?>
    <form method="post" action="http://site-a.com">
    <input type="hidden" name="forward" value="1"/>
    <input type="submit" value="Sign In"/>
    </form>
    <?php } ?>
  </body>
</html>

On the sister site, things are a little different.

  1. Same old database session.
  2. Remember how the main site will redirect back with the ?sess=XYZ session ID? This part will “reload and synchronize the session”.
  3. Some dummy HTML again.
  4. Kind of an optional thing to do, but highly recommended – We get rid of the ?sess=XYZ parameter, don’t allow the user to hit the back button and mess things up.
  5. Dummy – Show what is in the session.
  6. Show the “login form” if the user is not signed in.

Go ahead – Access this page, and see how it creates a new session entry in the database, but with no data.

 

5) SIGN IN

Lastly, click on the “Sign In” button. It should:

  • POST to http://site-a.com.
  • Site A will then redirect back to http://site-b.com/?sess_id=XYZ.
  • Site B registers the new session ID.
  • Remove the trailing parameters and redirect to http://site-b.com.

Done – The “handshake” is complete and both sites now share the same session.

 

USEFUL BITS & LINKS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

SECURITY CHECKS

Yes, cross-domain sessions work, but it is not immune to attacks. Anyone can just brute force and guess the session ID http://site-b.com/?sess_id=ABC. So it is best that you implement your own CSRF checks – On Site B, generate a random token before redirecting to Site A for login. On login, also verify the token.

 

 

I WANT ALL SITES TO “SYNCHRONIZE” IN ONE GO!

Looking to sign-in on the main site, and all the sister sites will be automatically “synchronized”? That is called “single sign-on” (SSO). But too bad, I can’t think of a smart way to do so. The possible mechanics:

  • Go on a redirect rampage after sign in.
  • Mess with iframes and 3rd party cookies.
  • Cross-domain AJAX calls – Check this out.

Let me know in the comments below if you have any genius ideas.

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Leave a Comment

Your email address will not be published. Required fields are marked *