Sessions, Users, and Permissions

Sessions, Users, and Permissions

Another interesting feature of the WebSnap architecture is its support for sessions and users. Sessions are supported using a classic approach: temporary cookies. These cookies are sent to the browser so that following requests from the same user can be acknowledged by the system. By adding data to a session instead of an application adapter, you can have data that depends on the specific session or user (although a user can run multiple sessions by opening multiple browser windows on the same computer). For supporting sessions, the application keeps data in memory, so this feature is not available in CGI programs.

Using Sessions

To underline the importance of this type of support, I built a WebSnap application with a single page showing both the total number of hits and the total number of hits for each session. The program has a SessionService component with default values for its MaxSessions and DefaultTimeout properties. For every new request, the program increases both an nHits private field of the page module and the SessionHits value of the current session:

procedure TSessionDemo.WebAppPageModuleBeforeDispatchPage(Sender: TObject;
  const PageName: String; var Handled: Boolean);
  // increase application and session hits
  Inc (nHits);
  WebContext.Session.Values ['SessionHits'] :=
    Integer (WebContext.Session.Values ['SessionHits']) + 1;

The WebContext object (of type TWebContext) is a thread variable created by WebSnap for each request. It provides thread-safe access to other global variables used by the program.

The associated HTML displays status information by using both custom tags evaluated by the OnTag event of the page producer and script evaluated by the engine. Here is the core portion of the HTML file:

<h3>Plain Tags</h3>
<p>Session id: <#SessionID>
<br>Session hits: <#SessionHits></p>
<p>Session hits (via application): <%=Application.SessionHits.Value%>
<br>Application hits: <%=Application.Hits.Value%></p>

The parameters of the output are provided by the OnTag event handler and the fields' OnGetValue events:

procedure TSessionDemo.PageProducerHTMLTag(Sender: TObject; Tag: TTag;
  const TagString: String; TagParams: TStrings; var ReplaceText: String);
  if TagString = 'SessionID' then
    ReplaceText := WebContext.Session.SessionID
  else if TagString = 'SessionHits' then
    ReplaceText := WebContext.Session.Values ['SessionHits']
procedure TSessionDemo.HitsGetValue(Sender: TObject; var Value: Variant);
  Value := nHits;
procedure TSessionDemo.SessionHitsGetValue(Sender: TObject; var Value: Variant);
  Value := Integer (WebContext.Session.Values ['SessionHits']);

The effect of this program is visible in Figure 20.13, where I've activated two sessions in two different browsers.

Click To expand Figure 20.13: Two instances of the browser operate on two different sessions of the same WebSnap application.

In this example, I used both the traditional WebBroker tag replacement and the newer WebSnap adapter fields and scripting, so that you can compare the two approaches. Keep in mind that they are both available in a WebSnap application.

Requesting Login

In addition to generic sessions, WebSnap also has specific support for users and login-based authorized sessions. You can add to an application a list of users (with the WebUserList component), each with a name and a password. This component is rudimentary in the data it can store. However, instead of filling it with your list of users, you can keep the list in a database table (or in another proprietary format) and use the WebUserList component's events to retrieve your custom user data and check the user passwords.

You'll generally also add to the application the SessionService and EndUserSessionAdapter components. At this point, you can ask the users to log in, indicating for each page whether it can be viewed by everyone or only by logged-in users. This is accomplished by setting the wpLoginRequired flag in the constructor of the TWebPageModuleFactory and TWebAppPageModuleFactory classes in the web page unit's initialization code.


Rights and publication information is included in the factory rather than in the WebPageModule because the program can check the access rights and list the pages even without loading the module.

When a user tries to see a page that requires user identification, the login page indicated in the EndUserSessionAdapter component is displayed. You can create such a page easily by creating a new web page module based on an AdapterPageProducer and adding to it the LoginFormAdapter. In the page's editor, add a field group within a form, connect the field group to the LoginFormAdapter, and add a command group with the default Login button. The resulting login form will have fields for the username and its password, and also for the requested page. This last value is automatically filled with the requested page, in case this page required authorization and the user wasn't already logged in. This way, a user can immediately reach the requested page without being bounced back to a generic menu.

The login form is typically not published, because the corresponding Login command is already available when the user isn't logged in to the system; when the user logs in, it is replaced by a Logout command. This command is obtained by the standard script of the web page module, particularly the following:

<% if (EndUser.Logout != null) { %>
<%   if (EndUser.DisplayName != '') { %>
  <h1>Welcome <%=EndUser.DisplayName %></h1>
<%   } %>
<%   if (EndUser.Logout.Enabled) { %>
  <a href="<%=EndUser.Logout.AsHREF%>">Logout</a>
<%   } %>
<%   if (EndUser.LoginForm.Enabled) { %>
  <a href=<%=EndUser.LoginForm.AsHREF%>>Login</a>
<%   } %>
<% } %>

There isn't much else to say about the WSnapUsers application, because it has almost no custom code and settings. This script for the standard template demonstrates the access to the user data.

Single Page Access Rights

In addition to making pages require a login for access, you can give specific users the right to see more pages than others. Any user has a set of rights separated by semicolons or commas. The user must have all the rights defined for the requested page. These rights, which are generally listed in the ViewAccess and ModifyAccess properties of the adapters, indicate respectively whether the user can see the given elements while browsing or can edit them. These settings are granular and can be applied to entire adapters or specific adapter fields (notice I'm referring to the adapter fields, not the user interface components within the designer). For example, you can hide some of a table's columns from given users by hiding the corresponding fields (and also in other cases, as specified by the HideOptions property).

The global PageDispatcher component also has OnCanViewPage and OnPageAccessDenied events, which you can use to control access to various pages of a program within the program code, allowing for even greater control.

Part I: Foundations