Delphi Labs: DataSnap XE - Authentication and Authorization

By: Pawel Glowacki

Abstract: In this lab exercise we are going to use Delphi XE to explore new DataSnap support for authentication and authorization.

    Introduction

One of the first architectural concerns during the design of a multitier system is security. Systems communicating over the Internet are especially vulnerable to different kinds of malicious attacks. The security spans all layers of a system design from transport layer to fine-grained, selective access to the actual system functionality based on roles. For example in a system that manages client orders one could imagine two types of access rights: a lower level access where authenticated user can only browse the information without a possibility to make any changes and a higher level access where authenticated user is allowed to make modification to the underlying data.

This type of fine-grained security is typically achieved in two steps.

  • Authentication is the process of verifying the identity of a user based on username and password
  • Authorization is the process of granting or denying the authenticated user access to server-side resources

The DataSnap framework implements support for authentication and authorization through the “TDSAuthenticationManager” component introduced in RAD Studio XE.

    Building the Server

Click on the “File – New – Other” menu and inside the “New Items” dialog double-click on the “DataSnap Server” wizard in “Delphi Projects – DataSnap Server” category to create a standalone Delphi DataSnap server application.

Hide image
DataSnapServerWiz

In the first tab of the wizard keep the default project type “VCL Forms Application” and on the second tab make sure to check “Authentication” and “Authorization” checkboxes.

Hide image
Click to see full-sized image

expand view>>

Keep all the default values on the remaining wizard screens and click on “Finish” to generate the DataSnap server project.

Select “File – Save All” to save all the files in the new project generated by the wizard. In my case I am going to create a new “C:\DataSnapLabs\AuthenticationAndAuthorization\” folder, give the main unit of the project “FormServerUnit” name, keep default names for other units (typically “ServerMethodsUnit1” and “ServerContainerUnit1”) and name project “SecureDSServer”.

Double-click on the server container unit in the Project Manager to display the form designer. Notice that the “DataSnap Server” wizard generated additionally to normal server components also “DSAuthenticationManager1” component.

Hide image
Click to see full-sized image

expand view>>

Notice also that “DSTCPServerTransport1.AuthenticationManager” property is now pointing to this new component.

If you switch to the code editor, for example pressing “F12”, you will also see that the wizard generated almost empty implementations of “OnUserAuthenticate” and “OnUserAuthorize” events.

procedure TServerContainer2.DSAuthenticationManager1UserAuthenticate(
  Sender: TObject; const Protocol, Context, User, Password: string;
  var valid: Boolean; UserRoles: TStrings);
begin

  valid := True;
end;

procedure TServerContainer2.DSAuthenticationManager1UserAuthorize(
  Sender: TObject; EventObject: TDSAuthorizeEventObject;
  var valid: Boolean);
begin

  valid := True;
end;

The minimal responsibility of the programmer is to set the “valid” parameter to true or false. Note the “var” modifier, which tells us, that the value that we assign to this parameter will persist beyond the local scope of these procedures. If we set “valid” to “false” in the authentication event no client will be able to connect to our DataSnap server. By default both implementations set “valid” to “true”, which effectively does not implement any restrictions on what clients can connect to our server and what server methods can be called. It is like there is no security - all clients have full access to all server methods.

Let’s start from adding authenticated users to roles. To keep the implementation simple, we are only going to check the value of “User” parameter passed to “Authenticate” event. If the “User” property is empty, then we set “valid” parameter to “false” preventing user from being able to call any server methods, otherwise we set “valid” parameter to “True”. If you want to call any of the server methods, you need to provide a non-empty username.

That’s the first step. The second step is to add authenticated users to roles. We are going to define a special role: “admins”. Any authenticated user will be added to the “admins” role and if the username is “admin”.

The last objective is to remove the “DSAuthenticationManager1UserAuthenticate” event implementation, which would effectively override our security settings and authorize calls to arbitrary server method for any authenticated user. Just remove the body of the event, which is just one line: “value := True;” and save. All empty events will automatically disappear.

I have removed “DSAuthenticationManager1UserAuthorize” event and modified the authentication events as follows:

procedure TServerContainer2.DSAuthenticationManager1UserAuthenticate(
  Sender: TObject; const Protocol, Context, User, Password: string;
  var valid: Boolean; UserRoles: TStrings);
begin
  valid := User <> '';

  if User = 'admin' then
    UserRoles.Add('admins');
end;

There are two ways to implement role-based access to server methods. You can either use “DSAuthenticationManager.Roles” property or set permissions at design-time using Object Inspector, or you can do the same thing in code decorating server methods class and server methods with “TRoleAuth” custom attribute.

In this example we are going to use the later approach and use custom attributes in code.

Open server methods unit in the editor and add to the interface “uses” clause the “DSAuth” unit, where the “TRoleAuth” custom attribute is implemented. Now you can decorate individual server methods (or the whole server class) with this attribute. The first parameter of the “TRoleAuth” attribute takes a string with “AuthorizedRoles” and the second, optional parameter contains “DeniedRoles”. For demo purposed let’s restrict access to “ReverseString” method, so only users that are “admins” can call this method.

Here is my modified version of server methods unit interface section. The implementation remains the same.

unit ServerMethodsUnit2;

interface

uses
  SysUtils, Classes, DSServer, DSAuth; // ß added “DSAuth” unit

type
{$METHODINFO ON}
  // [TRoleAuth('admins']   // ß it is also possible to decorate the whole class instead of individual methods
  TServerMethods2 = class(TComponent)
  private
    { Private declarations }
  public
    { Public declarations }
    function EchoString(Value: string): string;

    [TRoleAuth('admins']   // ß custom attribute
    function ReverseString(Value: string): string;
  end;
{$METHODINFO OFF}

implementation

uses StrUtils;

function TServerMethods2.EchoString(Value: string): string;
begin
  Result := Value + ' ' + Value;
end;

function TServerMethods2.ReverseString(Value: string): string;
begin
  Result := StrUtils.ReverseString(Value);
end;

end.

Our DataSnap server with role-based security is now ready. It is time to implement the client.

Right-click on the project group node in the Project Manager and select “Add New Project”. Select “VCL Forms Application” from “Delphi Projects” category and “Save All”. It is best to save new files in the same folder as server project – in my case in “C:\DataSnapLabs\AuthenticationAndAuthorization\” folder. You can name the client’s main form unit “FormClientMain”, the whole project “SecureDSClient” and project group: “SecureDS”.

Before we can implement the client, the server has to be running. Right-click on the project group and select “Build All”. Make sure that the server project is active (its name should be displayed in “bold” in Project Manager) and select “Run -> Run Without Debugging”. Minimize the server window. The server needs to be running during the client development.

    Building the Client

At this stage our project group consists of a server and a client project. The server is running and we need to implement the client. Let’s start from adding a “DataSnap Client Module” to our client.

Make sure that client project is active in the Project Manager and click on the “File – New – Other” menu and select “DataSnap Client Module” from the “Delphi Projects -> DataSnap Server” category.

Hide image
NewDSClientModule

On the first screen of the wizard keep the default “localhost” as DataSnap server location.

On the second screen keep the default “DataSnap standalone server” as the server project type.

On the third screen keep the default “tcp/ip” as the connection protocol.

On the last, fourth screen of the wizard we can test the connection to the server. By default the username and password are empty. If you click on the “Test Connection” button with an empty username, you would get the following error:

Hide image
Click to see full-sized image

This is exactly what we are expecting! Users with empty “user name” are not authenticated to connect to our server.

expand view>>

Let’s enter any user name (but not “admin”!), for example “guest”, and click “Test Connection” again.

This time we should be able to successfully connect to the server.

Hide image
Click to see full-sized image

Click on “Finish” to complete the wizard. Select “Save All” from “File” menu and keep default names of two new units added by the wizard: typically “ClientClassesUnit1” and “ClientModuleUnit1”.

expand view>>

Now it is time to build a simple user interface for our client application.

Drop two buttons and two edits on the client form. Name them “ButtonEcho”, “EditEcho”, “ButtonReverse” and “EditReverse”.

Select “File -> Use Unit” and add both files generated by the wizard to the client form unit and implement “OnClick” event handlers for both buttons.

My implementation of the client form looks like this:

// …

implementation

uses ClientClassesUnit1, ClientModuleUnit1;

{$R *.dfm}

procedure TForm3.Button1Click(Sender: TObject);
begin
  EditReverse.Text :=
    ClientModule1.ServerMethods2Client.ReverseString(EditReverse.Text);
end;

procedure TForm3.ButtonEchoClick(Sender: TObject);
begin
  EditEcho.Text :=
    ClientModule1.ServerMethods2Client.EchoString(EditEcho.Text);
end;

end.

Run the client and test its functionality.

If you click on the “Echo” button, you should see the result of the call displayed in the “echo” edit.

If you click on the “Reverse” button, you should receive the error saying that the user named – in my case – “guest” is not authorized to perform the requested operation.

Hide image
Click to see full-sized image

Again this is exactly what we were expecting!

expand view>>

During the process of adding a DataSnap Client Module to our client application, the wizard has embedded the username and password in the generated code.

Go to the ClientModuleUnit1 and select “SQLConnection1” component in the form designer. In the Object Inspector open its “Params” property and you can see the port number, username and password stored there.

Hide image
connection props

If you change the value of “DSAuthenticationUser” to “admin” and rerun the client you should be able to call both server methods.

    Summary

In this “Delphi Labs” episode we have looked into new DataSnap XE support for Authentication and Authorization.

In this example we have built a test server and a client that communicates over the TCP/IP. The server implements an authentication policy preventing users with empty username from connecting. We have also implemented role-based security and made sure that only user named “admin” can access “ReverseString” server method.

The source code for this article is available at the Code Central (http://cc.embarcadero.com/item/28226) and the video version of this demo is available on YouTube (http://www.youtube.com/watch?v=t87WTYgvU18 and http://www.youtube.com/watch?v=0EzQrWvfKmM).

More information about "Delphi Labs" is available at http://blogs.embarcadero.com/pawelglowacki

Server Response from: ETNASC03