TutWebAppServer Lesson 6 - Simple login and session data

From Overbyte
Jump to navigation Jump to search

During this lesson, you will learn how to handle session data.

As you probably know, HTTP is a stateless protocol. This means that each request is totally indepedent from each other as far as HTTP protocol is concerned. A web application is made of a lot of HTTP requests as the user interacts with the application. Because of the stateless nature of the HTTP protocol, your web application cannot rely on HTTP to have each page of the application know it is part of a single application used by a particular user at a given time. It is the application programmer's responsibility to make the application pages seen as a whole application, that is having a page content or behaviour affected by what the user has done previously with the application.

The application programmer has the need to have a data structure, at server side, with values private to a given user for the duration of his activity with the application. Of course since we are building a server application, we can have many users connected at the same time. The developer need a different data instance for each user. We will refer to this kind of data as "session data" in the remaining lessons.

Don't be scary, ICS HttpAppServer component and the user browser will help you achieve that goal.

From the user perspective, a session begin when he starts working with the application and ends when he stop working with the application. Usually the user is presented a login screen at the start of his working session, and simply close his browser or select some "logout" link to end his session.

Login screen is not mandatory. You may want to begin a session with any page from the application and create the session from there without ever asking the user to identify itself. Consider this as a kind of login where actually no usercode/password is checked but everything else is done.

In this lesson we will create a simple login page, create a data structure to hold data for the user session, create a simple dynamic page protected by the login and showing data saved in the session. The later will be the page shown immediately after login.

Steps

  1. Create a new template containing the login form. We do a dynamic page because frequently login is done from the main page which almost contain dynamic data. For simplicity here our page will only show the current time at server side. Enter the following code into the body part of the document:
    <form action="/DoSimpleLogin.html"
    method="GET"
    enctype="application/x-www-form-urlencoded">
    Usercode <input type="TEXT" name="UserCode"><br>
    Password <input type="PASSWORD" name="Password"><br>
    <input type="SUBMIT" value="Login"><br>
    <br>
    <br>Server time is <#ServerTime>
    </form>
    Save the HTML file in the templates folder under the name "SimpleLogin.html".
  2. Create a new template containing the simple page accessible only after login and showing some session data. Here after is the HTML code you have to enter into the body part of the document:
    If you see this page, then you have a valid session.<br>
    You logged at <#LoginTime><br>
    Your user code is <#UserCode><br>
    You already saw this page <#PageCount> times since you logged in.<br>
    Save the HTML file in the templates folder under the name "SimpleMainPage.html".
  3. Create a new Delphi unit "OverbyteIcsTutWebAppServerSimpleLogin.pas". You will write the classes TUrlHandlerSimpleLogin and TUrlHandlerDoSimpleLogin containing the code to handle the login process: one class for the login form and one class for the login form action. You will also write the class TUrlHandlerSimpleMainPage to handle the main page, that is the result page shown when login is OK.
  4. Map the classes to the URLs. In TTutWebAppServerMainForm.FormShow, add the following lines after the similar already there:
    HttpAppSrv1.AddGetHandler('/SimpleLogin.Html', TUrlHandlerSimpleLogin);
    HttpAppSrv1.AddGetHandler('/DoSimpleLogin.html', TUrlHandlerDoSimpleLogin);
    HttpAppSrv1.AddGetHandler('/SimpleMainPage.html', TUrlHandlerSimpleMainPage);
  5. Create the supporting 3 classes. For now, we just build basic code which doesn't do an actual login. This is just to verify that everything is OK until now. The class declaration part of unit "OverbyteIcsTutWebAppServerSimpleLogin.pas" looks like:
    TUrlHandlerSimpleLogin = class(TUrlHandler)
    public
    procedure Execute; override;
    end;

    TUrlHandlerDoSimpleLogin = class(TUrlHandler)
    public
    procedure Execute; override;
    end;

    TUrlHandlerSimpleMainPage = class(TUrlHandler)
    public
    procedure Execute; override;
    end;
    And the implementation looks like:
    procedure TUrlHandlerSimpleLogin.Execute;
    begin
    AnswerPage(’’, NO_CACHE, 'SimpleLogin.html', nil,
    ['ServerTime', DateTimeToStr(Now)]);
    Finish;
    end;

    procedure TUrlHandlerDoSimpleLogin.Execute;
    var
    Location : String;
    begin
    Location := '/SimpleMainPage.html';
    AnswerString('302 moved', ’’,
    'Location: ' + Location + #13#10 + NO_CACHE,
    '<A HREF="' + Location + '">Click here</A>');
    Finish;
    end;

    procedure TUrlHandlerSimpleMainPage.Execute;
    begin
    AnswerPage(’’, NO_CACHE, 'SimpleMainPage.html', nil, []);
    Finish;
    end;
    Now you can hit F9 to compile and run the server. Point your browser to http://127.0.0.1:20080/SimpleLogin.html. You should see the login page displayed in the browser with the server time properly displayed. Enter any value in usercode and password fields (we don't check those values yet) and click the login button. You should see the SimpleMainPage displayed.
  6. Since the login is not working yet, you can verify that you have an unrestricted access to SimpleMainPage.html: Restart your browser and enter http://127.0.0.1:20080/SimpleMainPage.html. You can see that page normally. This is the expected behaviour now. We will change this to protect the page by the login: the user will be forced to pass by the login process to successfuly access the page.
  7. Now we will create a new class in a new Delphi unit. This class will be our session data. Do File / New / Other / Delphi Project / Delphi Files / Unit. Save it under the name OverbyteIcsTutWebAppServerSessionData.pas.
  8. For now, we will use very simple session data: we will keep usercode and login time so that it is available during the whole session. Create a class like this:
    TAppSrvSessionData = class(TWebSessionData)
    private
    FUserCode: String;
    FLoginTime: TDateTime;
    FLoggedOn: Boolean;
    published
    property UserCode : String read FUserCode write FUserCode;
    property LoginTime : TDateTime read FLoginTime write FLoginTime;
    property LoggedOn : Boolean read FLoggedOn write FLoggedOn;
    end;
    Every data you want in your session data must be declared as published. This is fundamental to have persistance.
  9. Register your session data class. Add this code at the end of the unit, just before the final "end.".
    initialization
    RegisterClass(TAppSrvSessionData);
  10. Add OverbyteIcsTutWebAppServerSessionData unit to OverbyteIcsTutWebAppServerSimpleLogin.pas uses clause.
  11. Change TUrlHandlerSimpleLogin.Execute to create the session when the user request the login page.
    procedure TUrlHandlerSimpleLogin.Execute;
    var
    MySessionData : TAppSrvSessionData;
    Headers : String;
    begin
    if not ValidateSession then begin
    Inc(GSessionDataCount);
    MySessionData := TAppSrvSessionData.Create(nil);
    MySessionData.Name := 'MySessionData' + IntToStr(GSessionDataCount);
    Headers := NO_CACHE + CreateSession(’’, 0, MySessionData);
    end
    else begin
    MySessionData := WSession.SessionData as TAppSrvSessionData;
    Headers := NO_CACHE;
    end;
    MySessionData.LoggedOn := FALSE;

    AnswerPage(’’, Headers, 'SimpleLogin.html', nil,
    ['ServerTime', DateTimeToStr(Now)]);
    Finish;
    end;
    The code above check for a valid session. If a valid session exists, then it is reused, otherwise a new one is created with new session data. Note that the session must be given a unique name. That's why we used the global integer variable GSessionDataCount which is incremented when a new session is created.
  12. Create a global variable like this:
    var
    GSessionDataCount : Integer;
  13. Modify the code in TUrlHandlerDoSimpleLogin to extract usercode and password, and verify them against the user database. Since we are in a simple tutorial, we won't use a database but simply hard code a test for usercode "root" and password "admin". You'll easily adapt the code to fetch password from any database and verify validity more realisticly. The code is like this:
    procedure TUrlHandlerDoSimpleLogin.Execute;
    var
    UserCode : String;
    Password : String;
    Location : String;
    SessionData : TAppSrvSessionData;
    begin
    if not ValidateSession then
    Location := '/SimpleLogin.html'
    else begin
    SessionData := WSession.SessionData as TAppSrvSessionData;
    ExtractURLEncodedValue(Params, 'Password', Password);
    ExtractURLEncodedValue(Params, 'UserCode', UserCode);

    if not (SameText(UserCode, 'root') and SameText(Password, 'admin')) then begin
    DeleteSession;
    Location := '/SimpleLogin.html';
    end
    else begin
    SessionData.LoginTime := Now;
    SessionData.UserCode := UserCode;
    SessionData.LoggedOn := TRUE;
    Location := '/SimpleMainPage.html';
    end;
    end;
    AnswerString('302 moved', ’’,
    'Location: ' + Location + #13#10 + NO_CACHE,
    '<A HREF="' + Location + '">Click here</A>');
    Finish;
    end;
    In the above code, we do a serie of checks and then redirect to some page according to the check results. For simplicity we redirect to the SimpleLogin page if anything is wrong or missing and to SimpleMainPage if usercode and password are OK.
    We first check for a valid session. We have a valid session if the user already loaded the SimpleLogin page because we created the session there.
    Then we extract usercode and password from the request parameters (they are comming from the HTML form) and verify their values. If not OK, we redirect to SimpleLogin. If correct, we save UserCode and Password into session data and we set the LoggedOn flag in the session data.
    The idea behing this LoggedOn flag is that we can have a valid session because the user has seen the login page, but not authenticated the user since the user has not given his usercode and password. In this tutorial, we only create the session from the login page, but as we have seen above, the session could be created anywhere in the application. Probably at many places, we don't care about who is the user and at some place we need to authenticate the user before continuing. We have to make distinction between having a valid session and having an authenticated user. This is common is eCommerce website. We need to authenticate the user only to checkout his shopping cart. The process of shopping can be conduced without actually knowing who is the user. The shopping cart is just part of the session data.
    You can hit F9 to compile and run the server. Point you browser to http://127.0.0.1:20080/SimpleLogin.html, enter the usercode "john" and password "abc" and click login. You should get back to the SimpleLogin page since the usercode and password is invalid. Try with "root" and "admin". You should see SimpleMainPage.
  14. Now that we have our login process operational, we will protect SimpleMainPage from anonymous access. That is we will force the user to be logged on before showing the actual page. To do that, we simply have to validate the session and check the LoggedOn flag. If not OK, we will redirect the user to the login page.
    Modify the code in TUrlHandlerSimpleMainPage.Execute so that it looks like:
    procedure TUrlHandlerSimpleMainPage.Execute;
    var
    Location : String;
    SessionData : TAppSrvSessionData;
    begin
    Location := ’’;
    SessionData := nil;
    if not ValidateSession then
    Location := '/SimpleLogin.html'
    else begin
    SessionData := WSession.SessionData as TAppSrvSessionData;
    if not SessionData.LoggedOn then
    Location := '/SimpleLogin.html';
    end;

    if Location = ’’ then
    AnswerPage(’’, NO_CACHE, 'SimpleMainPage.html', nil,
    ['UserCode', SessionData.UserCode,
    'LoginTime', DateTimeToStr(SessionData.LoginTime)])
    else
    AnswerString('302 moved', ’’,
    'Location: ' + Location + #13#10 + NO_CACHE,
    '<A HREF="' + Location + '">Click here</A>');
    Finish;
    end;
    Hit F9 to compile and run the server. Point your browser to http://127.0.0.1:20080/SimpleMainPage.html. You should be redirected to SimpleLogin page since you have not yet logged on. Enter a "root" and "admin" as usercode and password. You should see the SimpleMainPage. Navigate somewhere else without closing your browser and then enter http://127.0.0.1:20080/SimpleMainPage.html. This time you should see the page immediately since you already logged in. If you close your browser or close the server program, you'll be again redirected to the login page. Our goal is reached: SimpleMainPage is protected by the login.
  15. There is one more step in the lesson showing how to use session data from a webpage. For simplicity, we will simple count the number of times the user has seen the SimpleMainPage since he logged in.
    Create a new property MainPageCount in TAppSrvSessionData class:
    private
    FMainPageCount : Integer;
    published
    property MainPageCount : Integer read FMainPageCount write FMainPageCount;
  16. Using the new MainPageCount property is easy. You have direct access to it and do whatever you like. Any change is persistent as long as the session exists. Change the code in TUrlHandlerSimpleMainPage.Execute so that it looks like:
    if Location = ’’ then begin
    SessionData.MainPageCount := SessionData.MainPageCount + 1;
    AnswerPage(’’, NO_CACHE, 'SimpleMainPage.html', nil,
    ['UserCode', SessionData.UserCode,
    'LoginTime', DateTimeToStr(SessionData.LoginTime),
    'PageCount', SessionData.MainPageCount])
    end




Summary

In this lesson you learned that session data is persistent data during the time the user intercat with your website. You learend how to create a class to define and store your session data. You learned how to create a session and use it to remember a user has been authenticated. You also used it to count the number of access for a given user.



Next lesson: Dynamic images
Previous lesson: Relocation
Tutorial presentation: TutWebAppServer