Session Tracking

by Hiro Iwashima and Jon Salz

This document is out of date.

ACS Documentation : ACS Kernel Documentation : Security Documentation : Session Tracking


Essentials

Introduction

HTTP is a stateless protocol, but nearly all nontrivial Web services are stateful. We need to provide a way to maintain state within a session. Such state includes the user's preferences, login information if any (although the solution needs to be general enough to handle anonymous sessions), and an identifier for the session itself. This session identifier absolutely must remain constant from the moment a user enters to the site to the moment he or she is done (even if the user logs in or switches to HTTPS in the meantime) so our clickstreaming software can ably analyze the users' behavior.

We need to provide different levels of security for different parts of this state:

The new security and session-tracking subsystem needs to consolidate the myriad ways in place now for maintaining and securing session and persistent state (including login information). It must remember the last time the user visited the site, so we can determine which material on the site is new since the user's last visit.

In reality, the session system has no way of knowing the identity of a person, but it makes the assumption that a series of requests from one browser, each within a specified interval, is from one person. The system recognizes session data by issuing and retrieving cookies from the client's browser. Included in the cookie is a token, a randomly generated string that acts like the password for the session. It verifies the information using the database, so someone cannot log in, get a session, muck with the cookie, then get access as someone else.

Because this system is instantiated upon every request made to the server, efficiency is crucial to the system. In order to achieve speed, we utilize global variables and AOL server caching mechanisms.

Design Tradeoffs

To provide the specifications, the system uses the global variable ad_conn to pass user session information between various procs. Though this makes the code more complex and harder to read, it allows for a cleaner implementation because we don't have to worry about passing all the variables from one part of the session to another. The global variable ad_conn in AOL Server exists from the beginning of the request to when the connection is closed, so the system can access the variables in any step of the way.

API

To fully show the API, we will go through a user session experience.

  1. User Richard first requests a page.
    1. sec_handler receives control and realizes that no cookie has been set for Richard. The procedure continues to create a unique session_id and a token, and sets the ad_session_id. It also realizes that the browser_id cookie doesn't exist, so it sets ad_browser_id cookie as well.

      The user information is stored in the database and also set in the global environment

    2. The page is processed and returned to the user.
  2. Richard then goes to a page that requires login... (calls ad_verify_and_get_user_id or ad_validate_security_info)
    1. sec_handler does the same thing as in step 1.1
    2. Within the loading of the page, ad_validate_security_info is called.

      It checks whether the token and session_id match the database. It then realizes that the user_id is null, so it returns 0 to the tcl page.

      The tcl page then redirects to a login page.

  3. Richard then logs into the system with his password. He also checks that he wants to stay logged in
    1. sec_handler sets ad_conn global variables
    2. The tcl page calls ad_user_login with a -forever flag
    3. ad_user_login calls ad_validate_security_info, which makes sure that the user is who they say they are. Then, it sets the user_id in the database and the global variable for the session_id given in the global variable. It then flushes the cache for this user, so the next time ad_validate_security_info is called, it has to actually perform the database hit and see that the user_id is set.

      Since the -forever flag is set, it then goes to assign a login_token inside the sec_login_token table. It then assigns the ad_user_login cookie to Richard's browser

    4. the tcl page redirects to the page that required user login in the first place
  4. Richard gets redirected to the page he wanted to see in the first place
    1. sec_handler sets ad_conn global variables from the cookie, and sets session_id, user_id, and token
    2. The tcl page calls ad_verify_and_get_user_id
    3. ad_verify_and_get_user_id checks the global variables with the ones stored in the database. Since the session_id, user_id, and token match, the user's user_id is sent back to the tcl page for process.
    4. Tcl page processes
  5. Richard happily sees his page, oblivious to the complex security checks, but then, decides to go see a page that contains sensitive material. (requires HTTPS)
    1. sec_handler sets ad_conn global variables from the cookie
    2. the tcl page calls ad_validate_security_info with a -secure flag.
    3. ad_validate_security_info checks to see if the secure_token is set, and since it is not, it proceeds to set both the secure_token and the login_token. (It limits the damage that could be done - see below in the Data Model section)
    4. Tcl page processes


Procedure Flow Chart

----------------
| Page Request |
----------------
      |        
      |            ---------------------   
      |------------| Request Processor |       Handles all redirects and url mappings
                   ---------------------
	                    |
	                    |
	              ---------------	   		   
                      | Sec_Handler |          sets ad_conn and cookies to the browser
	              ---------------
	                    |
      -----------------------
      |
      |
      |
 ------------   -----------------------------
 | TCL page |---| ad_verify_and_get_user_id |
 ------------   -----------------------------
                            |
			    |
	        -----------------------------
	        | ad_validate_security_info |  checks global variables against database.
	        -----------------------------
	                    |
			    |
                -----------------------------  
	        | ad_verify_and_get_user_id |  returns the user_id if security passed, 0 otherwise.
	        -----------------------------  
	                    |
		  	    |
      -----------------------
      |
      |
 ------------
 | TCL page |    
 ------------
      |
      |             --------
      --------------| User |
                    --------

Code API

sec_handler:
The main procedure that gets called is sec_handler, which gains control from the request processor and makes sure the global variable ad_conn is set correctly. If the handler recognizes a cookie, it sets the information appropriately. Otherwise, it goes through a process to create a session_id and set the cookie with the appropriate information.

ad_validate_security_info
The heart of the new security system is ad_validate_security_info, which examines the session information (including the user ID), returning 1 if it is valid or 0 if not. This procedure takes an optional switch, -secure, taking an argument. If -secure is true, the session won't be considered valid unless it's being conducted over HTTPS, and a valid secure token was provided (useful, e.g., for e-commerce applications). Typically client code will call ad_validate_security_info before doing anything else, redirecting or returning an error message if the session is deemed invalid.

The semantics of ad_get_user_id and ad_verify_and_get_user_id remain the same: ad_get_user_id does absolutely no checking that the user ID isn't forged, while ad_verify_and_get_user_id makes sure the user is properly logged in. Correspondingly, the new routine ad_get_session_id returns a session ID (which may be forged), whereas the new routine ad_verify_and_get_session_id first verifies that the token is valid. Both verify routines take an optional -secure switch, taking a Boolean (t/f) argument defaulting to f; if true, only secure (HTTPS) connections will be considered valid.

ad_verify_and_get_user_id
Calls ad_validate_security_info and returns the user_id, 0 otherwise.

ad_get_user_id
Returns the user_id, 0 otherwise. Does NOT check to see if the values given in the cookie match the ones in the database

ad_set_client_property
This procedure is used to set a session- or browser-level property. It takes three arguments: a module name, the name of the property, and the value of the property. In addition, the Boolean -browser switch, defaulting to f, determines whether the property should be persistent (i.e., browser-level); and the -secure switch, defaulting to f, determines whether the property should only be transmitted when a valid, secure session is in place. If it is supremely important that the property be set quickly, with no immediate database access, use -deferred t, causing the database hit to be deferred until after the HTTP connection is closed (so ad_set_client_property will return immediately). If the data should never be written to the database, use -persistent f.

ad_get_client_property
This call retrieves a property. It takes two arguments: module name and property name. Like ad_set_client_property it takes the optional -browser switch, defaulting to f. ad_get_client_property maintains a cache; to force the cache to be bypassed (in case accuracy is supremely important) specify -cache f. If only the cache should be queried (a database hit should never be incurred) use -cache_only t. If the property is not marked secure, ad_get_client_property does no checking to make sure the session is valid - it is the caller's responsibility to do this (usually using ad_validate_security_info).

Data Model

When appropriate we log and check the information against the following table (caching to minimize hits to the database):
create table sec_sessions (
    -- Unique ID (don't care if everyone knows this)
    session_id            integer primary key,
    user_id               references users,
    -- A secret used for unencrypted connections
    token                 varchar(50) not null,
    -- A secret used for encrypted connections only. not generated until needed
    secure_token          varchar(50),
    browser_id            integer not null,
    -- Make sure all hits in this session are from same host
    last_ip               varchar(50) not null,
    -- When was the last hit from this session? (seconds since the epoch)
    last_hit              integer not null
);
We populate secure_token only when we issue a secure token (the first time the client makes an access to the site over HTTPS).

Maintaining Session- and Browser-Specific State

In order to let programmers write code to preserve state on a per-session or per-browser basis without sending lots of cookies, we maintain the following tables:

create table sec_session_properties (
    session_id     references sec_sessions not null,
    module         varchar2(50) not null,
    property_name  varchar2(50) not null,
    property_value varchar2(4000),
    -- transmitted only across secure connections?
    secure_p       char(1) check(secure_p in ('t','f')),
    primary key(session_id, module, property_name),
    foreign key(session_id) references sec_sessions on delete cascade
);

create table sec_browser_properties (
    browser_id     integer not null,
    module         varchar2(50) not null,
    property_name  varchar2(50) not null,
    property_value varchar2(4000),
    -- transmitted only across secure connections?
    secure_p       char(1) check(secure_p in ('t','f')),
    primary key(browser_id, module, property_name)
);
A client module needing to save or restore session- or browser-specific state uses the new ad_get_client_property and ad_set_client_property routines, which manage access to the table (caching as appropriate). This way they don't have to set their own cookies, and as a bonus they don't have to worry about users tampering with contents!

In general, use session-level properties when you want the properties to expire when the current session ceases (e.g., items in a shopping cart). Use browser-level properties which the properties should never expire (e.g., user preferences).

One really neat thing about properties is that if secure_p is true (i.e., the secure_p flag was passed to ad_set_client_property - see above) the ad_get_client_property routine will refuse to access the information except when the connection is secure (HTTPS) and the secure token is correct. So the user can switch back and forth between HTTP and HTTPS without giving anything away, and hijackers cannot tamper with any state marked secure (even if they're sniffing for tokens). Note that this only works for session-level state for the moment - browser-level state isn't protected by any kind of token.

Cookie and Global Variables

We now use the following cookies to track sessions (ideally, no one will ever have to set another cookie again):

Configuration / Parameters

All inside of [ns/server/yourservername/security]

ParameterDescription
AllowPersistentLoginP Allow a person to be logged in forever?
TokenLength Length of Security Tokens
SessionTimeout how long can a session be inactive for before it times out? (in seconds)
SessionCookieReissue the period, in seconds, after which we should reissue the session_id cookie and update last_hit in the sessions table.
SessionInfoCacheInterval how long should we cache session information (using util_memoize)?
SessionLifetime how long after the last hit should we save information in the SessionLifetime table?
PreallocatedSessionPoolSize how many preallocated session should we store in the database?
PreallocatedSessionPoolUpdateInterval how often should we reallocate pooled sessions?
SessionSweepInterval how often should we sweep the sessions pool for old stale sessions?
PoolSequence.sec_id_seq pool values in the sec_id_seq sequence


Future Enhancements

We plan on modifying these cookies to support clusters of servers, i.e., sharing sessions amongst servers in a common domain (*.arsdigita.com).

Credits

This document (and the new security subsystem) ties together ideas introduced by lots of people, including: Thanks for their help and code!

Authors

Revision History

Document Revision # Action Taken, Notes When? By Whom?
0.1 Creation 08/30/2000 Hiro Iwashima
0.2 ad_conn moved to request processor docs 09/16/2000 Rafael Schloming

iwashima@mit.edu

Last Modified: sessions.html,v 1.1 2001/01/21 01:39:49 bquinn Exp