NASA

Contents

Intro

There have been some questions about how to build a server, with authorization and login management, where the users and their roles are defined in a database.

This blog post explains one way of doing that using the TkbmMWAuthorizationManager. Please refer to the previous post (REST easy with kbmMW #4 – Access management) for additional general information.

First we should have some server that needs login support. For this sample, I have chosen the FishFact REST server, which where built in the blog
REST easy with kbmMW #12 – Fishfact demo using HTTP.sys transport.

Adding login security

Based on that server we add a TjkbmMWAuthorizationManager to the main form (Unit1).

Then we need to determine how to store and access user information from the database. Since this sample already use the ORM to access the database, it makes sense to continue to do so for the user management.

Lets add a class describing a user:

  [kbmMW_Table('name:user')]
  TUser = class
  private
     FID:kbmMWNullable<string>;
     FName:kbmMWNullable<string>;
     FPassword:kbmMWNullable<string>;
     FRole:kbmMWNullable<string>;
  public
     [kbmMW_Field('name:"id", primary:true, generator:shortGuid',ftString,38)]
     property ID:kbmMWNullable<string> read FID write FID;

     [kbmMW_Field('name:"name"',ftString,50)]
     [kbmMW_NotNull]
     property Name:kbmMWNullable<string> read FName write FName;

     // A secure system should never store plain text passwords, but only SHA256 hashed ones.
     // In that case make room for 64 characters.
     [kbmMW_Field('name:"password"',ftString,50)]
     property Password:kbmMWNullable<string> read FPassword write FPassword;

     [kbmMW_Field('name:"role"',ftString,30)]
     property Role:kbmMWNullable<string> read FRole write FRole;
  end;

Notice the warning about the password. In this sample we store the unhashed plaintext password in the database. That is a NO NO in a production system. Instead one should store a hashed and salted version of the password… I’ll explain later how to modify the code to do that.

For now we accept that the password is unhashed in the database.

In the already existing Form.OnCreate event handler, we should let the ORM ensure that the user table is made available. In addition we also should define the roles that are accepted by this server.

In our sample, there are only two types of users… the anonymous ones and the ones that are logged in with administrator rights. Most of the functionality is made available for anonymous users to use, except one REST call, which require the administrative role.  But first things first:

procedure TfrmMain.FormCreate(Sender: TObject);
begin
     FORM:=TkbmMWORM.Create;
     FORM.OpenDatabase(kbmMWSQLiteConnectionPool1);
     FORM.CreateOrUpgradeTable(TUser);

     // Add the one single role this application server knows about except anonymous.
     AuthMgr.AddRole('AdminRole');

     kbmMWServer1.AutoRegisterServices;
end;

The interesting parts here is the CreateOrUpgradeTable call, which ensures there is a table named user in the database, and the definition of a role called AdminRole.

In Unit1 we should also remember to register the TUser class so kbmMW is aware about its existance. One place to do that is the initialization section of the form unit.

...
initialization
  TkbmMWRTTI.EnableRTTI([TUser]);
  kbmMWRegisterKnownClasses([TUser]);

end.

Now we must link the knowledge about the user table with the login process of the authorization manager. The crucial point is that the authorization manager is the supreme authority in relation to logins, and as such must know about the actors that are allowed to login. Thus the actors needs to be defined. It can either be done at startup time of the application server, where a complete list of known users are defined as actors towards the authorization manager, or alternatively it can be done on the fly on a need to know bases, which is what I have chosen to show here.

We use the OnLogin event of the authorization manager:

procedure TfrmMain.AuthMgrLogin(Sender: TObject; const AActorName,
  ARoleName: string; var APassPhrase: string;
  var AActor: TkbmMWAuthorizationActor; var ARole: TkbmMWAuthorizationRole;
  var AMessage: string);
var
   user:TUser;
begin
     // Lookup user with given name and password.
     user:=ORM.Query<TUser>(['Name','Password'],[AActorName,APassPhrase]);
     if user<>nil then
        try
           // Check if users role is defined. If not complain.
           ARole:=AuthMgr.Roles.Get(user.Role.Value);
           if ARole=nil then
              AMessage:='Role not supported'
           else
           begin
                // Check if actor exists, use it, else create one.
                AActor:=AuthMgr.GetActor(AActorName);
                if AActor=nil then
                   AActor:=AuthMgr.AddActor(AActorName,APassPhrase,ARoleName);
                AMessage:='User found and is allowed login';
           end;
        finally
           user.Free;
        end
     else
         AMessage:='User not found';
end;

Basically it use the ORM to lookup a user with the given name and password in the database. If one is found, it checks to see if the role that has been defined in the database on the user, exists.

If it does, then it attempts to figure out if the user has already been defined as an actor in the authorization manager. If not then one is defined and all is well.

What if the database is changed… For example if a user changes password? In such case you should not only update the password in the database but also update it in the actor representation in memory.

You can do something like this:

procedure TUnit1.UpdateUserPassword(const AUserName, ANewPassword:string);
var
  user:TUser;
begin
 AuthMgr.ChangeActorPassword(AUserName,ANewPassord);
 user:=ORM.Query<TUser>(['Name'],[AUserName]);
 if user<>nil then
    try
      user.Password:=ANewPassword;
      ORM.Update(user);
    finally
      user.Free;
    end;
end;

And if the user is to be deleted:

procedure TUnit1.RemoveUser(const AUserName:string);
begin
 AuthMgr.DeleteActor(AUserName);
 ORM.Delete<TUser>(['Name'],[AUserName]);
end;

Finally we should define exactly what should be protected by login.

For that we open Unit2.pas which contains the REST service, and choose one or more of the methods to protext. In this case GetSpecieByCategory will now require login as an administrative role, for it to be used.

     [kbmMW_Rest('method:get, path:"specieByCategory/{category}"')]
     [kbmMW_Auth('role:[AdminRole], grant:true')]
     function GetSpecieByCategory([kbmMW_Rest('value:"{category}"')] const ACategory:string):TBiolifeNoImage;

Also remember to add kbmMWSecurity to the interface uses clause of the unit. kbmMWSecurity.pas contains the definition of the kbmMW_Auth attribute.

Now we are done. Make sure to add a user with a password and a role of AdminRole to the user table, run the application server and try out the various REST calls.

The moment you try to this call:

http://localhost:1111/biolife/specieByCategory/Butterflyfish

You will be requested for a login by the browser. If you provide the user name and password matching a user in the database having the role AdminRole, you will be shown the result of the request. Otherwise you will only have access to all other REST calls which have no kbmMW_Auth attribute and as such are allowed to be called anonymously.

Hashing passwords

Remember that I mentioned storing (and transferring) plaintext passwords is a no no in production environments?

Hence we should encrypt the password before storage and transfer. However encryption typically means its possible to reverse the encryption, provided the encryption key can be guessed or hacked, which would reveal the password in plain text again. Since users may sometimes reuse the same password on multiple servers, we should make it as hard as possible for potential hackers to get back to the plaintext version of the password.

In a REST setup, its usually a web browser that decides how usernames and passwords are sent. The typical way is actually to leave all encryption to a SSL and user certificates and such to ensure that not only transmitted login data is scrambled, but also all other traffic between the browser and the server. Check the REST easy with kbmMW #3 – SSL blog post for information about one way to secure your REST application server with SSL and certificates.

And that is all fine and good, but we also have the storage part. We really shouldn’t store the password in plaintext.

So we will use one way encryption… also known as hashing. It basically calculates a (complex) sum of the original password. Since its a “sum”, its usually impossible to reverse the calculation back to the original password, provided a good secure hashing algorithm is used. Fortunately kbmMW provides native support for several secure hashing algorithms. One of the most used ones, that is generally considered secure, is called SHA256.

Now when we receive a password in the OnLogin event, we need to hash it before we do anything with it. It is super simple to do so.

Include kbmMWHashSHA256 in your uses clause.

var
  hashed:string;
begin
  hashed:=TkbmMWHashSHA256.HashAsString(APassPhrase,'somesaltvalue');
...
end;

somesaltvalue is some “secret” value you have in your application and that is unique for your application. It can be anything, but prefer a length string of scrambled random characters.

The idea behind a “salt” is that it makes it extremely difficult to attempt to bruteforce guessing the correlation between a plaintext attempt and a calculated SHA256 value. If you simply use the password by itself, then attackers has a much easier time attempting to guess the password, simply because they can try out all combinations of characters and match the hashed result with the sniffed hash value that you have hashed. Adding a salt, ensures that the attacker will have no chance in brute force attacking by trying out all combinations, because regardless of what the attacker attempts to find, it will never be the same as the value you have stored in the database due to the secret salt.

Now we have a hashed string, and that one can be stored in a database, and similarly every time we need to figure out if person has typed in the correct password, we first need to hash it server side (with the correct salt) and then attempt to look the hashed password (and username) up in the database.

Prologue

There are many more features in the authorization manager, which I have not explained here, but visit our site at http://www.components4developers.com, and look for the kbmMW documentations section for whitepapers.

If you like this, please share the word about kbmMW wherever you can and feel free to link, like, share and copy the posts of this blog to where you find they could be useful.

Oh… and whats about that featured image? It’s an image of the spiral galaxy M51a, also known as the whirlpool galaxy. Whirlpool is also the name chosen for one of the stronger hashing algorithms, for which there has still not been found any significant weaknesses or attack vectors.

Loading

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.