Authentication

Extracted from Wikipedia:

Authentication (from Greek: "real" or "genuine", from "author") is the act of confirming the truth of an attribute of a datum or entity. This might involve confirming the identity of a person or software program, tracing the origins of an artifact, or ensuring that a product is what its packaging and labeling claims to be. Authentication often involves verifying the validity of at least one form of identification.

Principles

How to handle authentication in a RESTful Client-Server architecture is a matter of debate.

Commonly, it can be achieved, in the SOA over HTTP world via:
- HTTP basic auth over HTTPS;
- Cookies and session management;
- Query Authentication with additional signature parameters.

We'll have to adapt, or even better mix those techniques, to match our framework architecture at best.

Each authentication scheme has its own PROs and CONs, depending on the purpose of your security policy and software architecture:

Criteria HTTPS basic auth Cookies+Session Query Auth.
Browser integration Native Native Via JavaScript
User Interaction Rude Custom Custom
Web Service use
(rough estimation)
95% 4% 1%
Session handling Yes Yes No
Session managed by Client Server N/A
Password on Server Yes Yes/No N/A
Truly Stateless Yes No Yes
Truly RESTful No No Yes
HTTP-free No No Yes

HTTP basic auth over HTTPS

This first solution, based on the standard HTTPS protocol, is used by most web services. It's easy to implement, available by default on all browsers, but has some known draw-backs, like the awful authentication window displayed on the Browser, which will persist (there is no LogOut-like feature here), some server-side additional CPU consumption, and the fact that the user-name and password are transmitted (over HTTPS) into the Server (it should be more secure to let the password stay only on the client side, during keyboard entry, and be stored as secure hash on the Server).

The supplied TSQLHttpClientWinHTTP and TSQLHttpClientWinINet clients classes are able to connect using HTTPS, and the THttpApiServer server class can send compatible content.

Session via Cookies

To be honest, a session managed on the Server is not truly Stateless. One possibility could be to maintain all data within the cookie content. And, by design, the cookie is handled on the Server side (Client in fact don’t even try to interpret this cookie data: it just hands it back to the server on each successive request). But this cookie data is application state data, so the client should manage it, not the server, in a pure Stateless world.

The cookie technique itself is HTTP-linked, so it's not truly RESTful, which should be protocol-independent. Since our framework does not provide only HTTP protocol, but offers other ways of transmission, Cookies were left at the baker's home.

Query Authentication

Query Authentication consists in signing each RESTful request via some additional parameters on the URI. See http://broadcast.oreilly.com/2009/12/principles-for-standardized-rest-authentication.html about this technique. It was defined as such in this article:

All REST queries must be authenticated by signing the query parameters sorted in lower-case, alphabetical order using the private credential as the signing token. Signing should occur before URI encoding the query string.

For instance, here is a generic URI sample from the link above:

 GET /object?apiKey=Qwerty2010

should be transmitted as such:

 GET /object?timestamp=1261496500&apiKey=Qwerty2010&signature=abcdef0123456789

The string being signed is "/object?apikey=Qwerty2010×tamp=1261496500" and the signature is the SHA256 hash of that string using the private component of the API key.

This technique is perhaps the more compatible with a Stateless architecture, and can also been implemented with a light session management.

Server-side data caching is always available. In our framework, we cache the responses at the SQL level, not at the URI level (thanks to our optimized implementation of GetJSONObjectAsSQL, the URI to SQL conversion is very fast). So adding this extra parameter doesn't break the cache mechanism.

Framework authentication

Even if, theoretically speaking, Query Authentication sounds to be the better for implementing a truly RESTful architecture, our framework tries to implement a Client-Server design.

In practice, we may consider two way of using it:
- With no authentication nor user right management (e.g. for local access of data, or framework use over a secured network);
- With per-user authentication and right management via defined security groups, and a per-query authentication.

According to RESTful principle, handling per-session data is not to be implemented in such architecture. A minimal "session-like" feature was introduced only to handle user authentication with very low overhead on both Client and Server side. The main technique used for our security is therefore Query Authentication, i.e. a per-URI signature.

If both AuthGroup and AuthUser are not available on the Server TSQLModel (i.e. if the aHandleUserAuthentication parameter was set to false for the TSQLRestServer. Create constructor), no authentication is performed. All tables will be accessible by any client, as stated in 19. As stated above, for security reasons, the ability to execute INSERT / UPDATE / DELETE SQL statement via a RESTful POST command is never allowed by default with remote connections: only SELECT can be executed via this POST verb.

On the Server side, a dedicated service, accessible via the ModelRoot/Auth URI is to be called to register an User, and create a session.

If authentication is enabled for the Client-Server process (i.e. if both AuthGroup and AuthUser are available in the Server TSQLModel, and the aHandleUserAuthentication parameter was set to true at the TSQLRestServer instance construction), the following security features will be added:
- Client should open a session to access to the Server, and provide a valid UserName / Password pair (see next paragraph);
- Each CRUD statement is checked against the authenticated User security group, via the AccessRights column and its GET / POST / PUT / DELETE per-table bit sets;
- Thanks to Per-User authentication, any SQL statement commands may be available via the RESTful POST verb for an user with its AccessRights group field containing AllowRemoteExecute=true;
- Each REST request will expect an additional parameter, named session_signature, to every URL. Using the URI instead of cookies allows the signature process to work with all communication protocols, not only HTTP.

Per-User authentication

On the Server side, two tables, defined by the TSQLAuthGroup and TSQLAuthUser classes will handle respectively per-group access rights, and user authentication.

The corresponding AccessRights column is a textual CSV serialization of the TSQLAccessRights record content, as expected by the TSQLRestServer.URI method. Using a CSV serialization, instead of a binary serialization, will allow the change of the MAX_SQLTABLES constant value.

The AuthUser table, as defined by the TSQLAuthUser class type.

Each user has therefore its own associated AuthGroup table, a name to be entered at login, a name to be displayed on screen or reports, and a SHA-256 hash of its registered password. A custom Data BLOB field is specified for your own application use, but not accessed by the framework.

By default, the following security groups are created on a void database:

AuthGroup POST SQL Auth Read Auth Write Tables R Tables W
Admin Yes Yes Yes Yes Yes
Supervisor No Yes No Yes Yes
User No No No Yes Yes
Guest No No No Yes No

Then the corresponding 'Admin', 'Supervisor' and 'User' AuthUser accounts are created, with the default 'synopse' password.

You MUST override those default 'synopse' passwords for each AuthUser record to a custom genuine value.

'Admin' will be the only group able to execute remote not SELECT SQL statements for POST commands (i.e. to have TSQLAccessRights. AllowRemoteExecute = true) and modify the Auth* tables (i.e. AuthUser and AuthGroup) content.

A typical JSON representation of the default security user/group definitions are the following:

[{"AuthUser":[
{"RowID":1,"LogonName":"Admin","DisplayName":"Admin","PasswordHashHexa":"67aeea294e1cb515236fd7829c55ec820ef888e8e221814d24d83b3dc4d825dd","GroupRights":1,"Data":null},
{"RowID":2,"LogonName":"Supervisor","DisplayName":"Supervisor","PasswordHashHexa":"67aeea294e1cb515236fd7829c55ec820ef888e8e221814d24d83b3dc4d825dd","GroupRights":2,"Data":null},
{"RowID":3,"LogonName":"User","DisplayName":"User","PasswordHashHexa":"67aeea294e1cb515236fd7829c55ec820ef888e8e221814d24d83b3dc4d825dd","GroupRights":3,"Data":null}]},
{"AuthGroup":[
{"RowID":1,"Ident":"Admin","SessionTimeout":10,"AccessRights":"11,1-256,0,1-256,0,1-256,0,1-256,0"},
{"RowID":2,"Ident":"Supervisor","SessionTimeout":60,"AccessRights":"10,1-256,0,3-256,0,3-256,0,3-256,0"},
{"RowID":3,"Ident":"User","SessionTimeout":60,"AccessRights":"10,3-256,0,3-256,0,3-256,0,3-256,0"},
{"RowID":4,"Ident":"Guest","SessionTimeout":60,"AccessRights":"0,3-256,0,0,0,0"}]}]

Of course, you can change AuthUser and AuthGroup table content, to match your security requirements, and application specifications. You can specify a per-table CRUD access, via the AccessRights column, as we stated above, speaking about the TSQLAccessRights record layout.

This will implement both Query Authentication together with a group-defined per-user right management.

Session handling

A dedicated RESTful service, available from the ModelRoot/Auth URI, is to be used for user authentication, handling so called sessions.

In mORMot, a very light in-memory set of sessions is implemented:
- The unique ModelRoot/Auth URI end-point will create a session after proper authorization;
- In-memory session allows very fast process and reactivity, on Server side;
- An integer session identifier is used for all authorization process, independently from the underlying authentication scheme (i.e. mORMot is not tied to cookies, and its session process is much more generic).

Those sessions are in-memory TAuthSession class instances. Note that this class does not inherit from a TSQLRecord table so won't be remotely accessible, for performance and security reasons.

The server methods should not have to access those instances directly, but rely on the SessionID identifier. The only access available is via the function TSQLRestServer.SessionGetUser(aSessionID: Cardinal): TSQLAuthUser method.

When the Client is about to close (typically in TSQLRestClientURI.Destroy), a GET ModelRoot/auth?UserName=...&Session=... request is sent to the remote server, in order to explicitly close the corresponding session in the server memory (avoiding most re-play attacks).

Note that each opened session has an internal TimeOut parameter (retrieved from the associated TSQLAuthGroup table content): after some time of inactivity, sessions are closed on the Server Side.

In addition, sessions are used to manage safe cross-client transactions:
- When a transaction is initiated by a client, it will store the corresponding client Session ID, and use it to allow client-safe writing;
- Any further write to the DB (Add/Update/Delete) will be accessible only from this Session ID, until the transaction is released (via commit or rollback);
- If a transaction began and another client session try to write on the DB, it will wait until the current transaction is released - a timeout may occur if the server is not able to acquire the write status within some time;
- This global write locking is implemented in the TSQLRest.AcquireWrite / ReleaseWrite protected methods, and used on the Server-Side by TSQLRestServer.URI;
- If the server do not handle Session/Authentication, transactions can be unsafe, in a multi-client concurrent architecture.

Therefore, for performance reasons in a multi-client environment, it's mandatory to release a transaction (via commit or rollback) as soon as possible.

Authentication schemes

Class-driven authentication

Authentication is implemented in mORMot via the following classes:

class Scheme
TSQLRestServerAuthenticationDefault mORMot secure authentication scheme, based on a proprietary dual-pass challenge and SHA-256 hashing
TSQLRestServerAuthenticationSSPI Windows authentication, via the logged user
TSQLRestServerAuthenticationNone Weak but simple authentication, based on user name

All those classes will identify a TSQLAuthUser record from a user name. The associated TSQLAuthGroup is then used later for authorization.

You can add you own custom authentication scheme by defining your own class, inheriting from TSQLRestServerAuthentication.

By default, no authentication is performed.

If you set the aHandleUserAuthentication parameter to true when calling the constructor TSQLRestServer.Create(), both default secure mORMot authentication and Windows authentication are available. In fact, the constructor executes the following:

constructor TSQLRestServer.Create(aModel: TSQLModel; aHandleUserAuthentication: boolean);
  (...)
  if aHandleUserAuthentication then
    // default mORMot authentication schemes
    AuthenticationRegister([
      TSQLRestServerAuthenticationDefault,TSQLRestServerAuthenticationSSPI]);
  (...)

In order to define one or several authentication scheme, you can call the AuthenticationRegister() and AuthenticationUnregister() methods of TSQLRestServer.

mORMot secure RESTful authentication

The TSQLRestServerAuthenticationDefault class implements a proprietary but secure RESTful 18.

Here are the typical steps to be followed in order to create a new user session via mORMot authentication scheme:
- Client sends a GET ModelRoot/auth?UserName=... request to the remote server;
- Server answers with an hexadecimal nonce contents (valid for about 5 minutes), encoded as JSON result object;
- Client sends a GET ModelRoot/auth?UserName=...&PassWord=...&ClientNonce=... request to the remote server, in which ClientNonce is a random value used as Client nonce, and PassWord is computed from the log-on and password entered by the User, using both Server and Client nonce as salt;
- Server checks that the transmitted password is valid, i.e. that its matches the hashed password stored in its database and a time-valid Server nonce - if the value is not correct, authentication fails;
- On success, Server will create a new in-memory session and return the session number and a private key to be used during the session (encoded as JSON result object);
- On any further access to the Server, a &session_signature= parameter is added to the URL, and will be checked against the valid sessions in order to validate the request.

Query Authentication is handled at the Client side in TSQLRestClientURI.SessionSign method, by computing the session_signature parameter for a given URL, according to the TSQLRestServerAuthentication class used.

In order to enhance security, the session_signature parameter will contain, encoded as 3 hexadecimal 32 bit cardinals:
- The Session ID (to retrieve the private key used for the signature);
- A Client Time Stamp (in 256 ms resolution) which must be greater or equal than the previous time stamp received;
- The URI signature, using the session private key, the user hashed password, and the supplied Client Time Stamp as source for its crc32 hashing algorithm.

Such a classical 3 points signature will avoid most man-in-the-middle (MITM) or re-play attacks.

Here is typical signature to access the root URL

 root?session_signature=0000004C000F6BE365D8D454

In this case, 0000004C is the Session ID, 000F6BE3 is the client time stamp (aka nonce), and 65D8D454 is the signature, checked by the following Delphi expression:

(crc32(crc32(fPrivateSaltHash,PTimeStamp,8),pointer(aURL),aURLlength)=aSignature);

For instance, a RESTful GET of the TSQLRecordPeople table with RowID=6 will have the following URI:

 root/People/6?session_signature=0000004C000F6DD02E24541C

For better Server-side performance, the URI signature will use fast crc32 hashing method, and not the more secure (but much slower) SHA-256. Since our security model is not officially validated as a standard method (there is no standard for per URI authentication of RESTful applications), the better security will be handled by encrypting the whole transmission channel, using standard HTTPS with certificates signed by a trusted CA, validated for both client and server side. The security involved by using crc32 will be enough for most common use. Note that the password hashing and the session opening will use SHA-256, to enhance security with no performance penalty.

In our implementation, for better Server-side reaction, the session_signature parameter is appended at the end of the URI, and the URI parameters are not sorted alphabetically, as suggested by the reference article quoted above. This should not be a problem, either from a Delphi Client or from a AJAX / JavaScript client.

On practice, this scheme is secure and very fast, perfect for a Delphi client.

Authentication using Windows credentials

By default, the hash of the user password is stored safely on the server side. This may be an issue for corporate applications, since a new user name / password pair is to be defined by each client, which may be annoying.

Since revision 1.18 of the framework, mORMot is able to use Windows Authentication to identify any user. That is, the user does not need to enter any name nor password, but her/his Windows credentials, as entered at Windows session startup, will be used.

If the SSPIAUTH conditional is defined (which is the default), any call to TSQLRestClientURI.SetUser() method with a void aUserName parameter will try to use current logged name and password to perform a secure Client-Server authentication. It will in fact call the class function TSQLRestServerAuthenticationSSPI.ClientSetUser() method.

In this case, the aPassword parameter will just be ignored. This will be transparent to the framework, and a regular session will be created on success.

Only prerequisite is that the TSQLAuthUser table shall contain a corresponding entry, with its LogonName column equals to 'DomainNameUserName' value. This data row won't be created automatically, since it is up to the application to allow or disallow access from an authenticated user: you can be member of the domain, but not eligible to the application.

Weak authentication

The TSQLRestServerAuthenticationNone class can be used if you trust your client (e.g. via a https connection). It will implement a weak but simple authentication scheme.

Here are the typical steps to be followed in order to create a new user session via this authentication scheme:
- Client sends a GET ModelRoot/auth?UserName=... request to the remote server;
- Server checks that the transmitted user name is valid, i.e. that it is available in the TSQLAuthGroup table - if the value is not correct, authentication fails
- On success, Server will create a new in-memory session and returns the associated session number (encoded as hexadecimal in the JSON result object);
- On any further access to the Server, a &session_signature= parameter is to be added to the URL with the correct session ID, and will be checked against the valid sessions in order to validate the request.

For instance, a RESTful GET of the TSQLRecordPeople table with RowID=6 will have the following URI:

 root/People/6?session_signature=0000004C

Here is some sample code about how to define this authentication scheme:

  // on the Server side:
  Server.AuthenticationRegister(TSQLRestServerAuthenticationNone);
  ...
  // on the Client side:
  TSQLRestServerAuthenticationNone.ClientSetUser(Client,'User');

The performance benefit is very small in comparison to TSQLRestServerAuthenticationDefault, so should not be used for Delphi clients.

Clients authentication

Client interactivity

Note that with this design, it's up to the Client to react to an authentication error during any request, and ask again for the User pseudo and password at any time to create a new session. For multiple reasons (server restart, session timeout...) the session can be closed by the Server without previous notice.

In fact, the Client should just use create one instance of the TSQLRestClientURI classes as presented in 6, then call the SetUser method as such:

      Check(Client.SetUser('User','synopse')); // use default user

Then an event handled can be associated to the TSQLRestClientURI. OnAuthentificationFailed property, in order to ask the user to enter its login name and password:

  TOnAuthentificationFailed = function(Retry: integer;
    var aUserName, aPassword: string): boolean;

Of course, if Windows Authentication is defined (see above), this event handler shall be adapted as expected. For instance, you may add a custom notification to register the corresponding user to the TSQLAuthUser table.

Authentication using AJAX

Some working JavaScript code has been published in our forum by a framework user (thanks, "RangerX"), which implements the authentication schema as detailed above. It uses jQuery, and HTML 5 LocalStorage, not cookies, for storing session information on the Client side.

See http://synopse.info/forum/viewtopic.php?pid=2995#p2995

The current revision of the framework contains the code as expected by this JavaScript code - especially the results encoded as 2 objects.

In the future, some "official" code will be available for such AJAX clients. It will probably rely on pure-pascal implementation using such an Object-Pascal-to-JavaScript compiler - it does definitively make sense to have Delphi-like code on the client side, not to break the ORM design. For instance, the Open Source DWS (DelphiWebScript) compiler matches our needs - see http://delphitools.info/tag/javascript

Authorization

Per-table access rights

Even if authentication is disabled, a pointer to a TSQLAccessRights record, and its GET / POST / PUT / DELETE fields, is sent as a member of the parameter to the unique access point of the server class:

procedure TSQLRestServer.URI(var Call: TSQLRestServerURIParams);

This will allow checking of access right for all CRUD operations, according to the table invoked. For instance, if the table TSQLRecordPeople has 2 as index in TSQLModel.Tables[], any incoming POST command for TSQLRecordPeople will be allowed only if the 2nd bit in RestAccessRights^.POST field is set, as such:

    case URI.Method of
    mPOST: begin       // POST=ADD=INSERT
      if URI.Table=nil then begin
      (...)
    end else
      // here, Table<>nil and TableIndex in [0..MAX_SQLTABLES-1]
      if not (URI.TableIndex in Call.RestAccessRights^.POST) then // check User
        Call.OutStatus := HTML_FORBIDDEN else
      (...)

Making access rights a parameter allows this method to be handled as pure stateless, thread-safe and session-free, from the bottom-most level of the framework.

On the other hand, the security policy defined by this global parameter does not allow tuned per-user authorization. In the current implementation, the SUPERVISOR_ACCESS_RIGHTS constant is transmitted for all handled communication protocols (direct access, GDI messages, named pipe or HTTP). Only direct access via TSQLRestClientDB will use FULL_ACCESS_RIGHTS, i.e. will have AllowRemoteExecute parameter set to true.

The light session process, as implemented by 18, is used to override the access rights with the one defined in the TSQLAuthGroup.AccessRights field.

Be aware than this per-table access rights depend on the table order as defined in the associated TSQLModel. So if you add some tables to your database model, please take care to add the new tables after the existing. If you insert the new tables within current tables, you will need to update the access rights values.

Additional safety

A AllowRemoteExecute: TSQLAllowRemoteExecute field has been made available in the TSQLAccessRights record to tune remote execution, depending on the authenticated user.

It adds some options to tune the security policy.

SQL remote execution

In our RESTful implementation, the POST command with no table associated in the URI allows to execute any SQL statement directly.

This special command should be carefully tested before execution, since SQL misuses could lead into major security issues. Such execution on any remote connection, if the SQL statement is not a SELECT, is unsafe. In fact, if it may affect the data content.

By default, for security reasons, this AllowRemoteExecute field value in SUPERVISOR_ACCESS_RIGHTS constant does not include reSQL. It means that no remote call will be allowed but for safe read-only SELECT statements.

Another possibility of SQL remote execution is to add a sql=.... inline parameter to a GET request (with optional paging). The reUrlEncodedSQL option is used to enable or disable this feature.

Last but not least, a WhereClause=... inline parameter can be added to a DELETE request. The reUrlEncodedDelete option is used to enable or disable this feature.

You can change the default safe policy by including reSQL, reUrlEncodedSQL or reUrlEncodedDelete in the TSQLAuthGroup.AccessRights field if an authentication user session. But since remote execution of any SQL statements can be unsafe, we recommend to write a dedicated server-side service (method-based or interface-based) to execute such statements.

Service execution

The reService option can be used to enable or unable the interface-based services feature of mORMot.

In addition to this global parameter, you can set per-service and per-method security via dedicated methods.

For method-based services, if authentication is enabled, any method execution will be processed only for signed URI.

You can use TSQLRestServer.ServiceMethodByPassAuthentication() to disable the need of a signature for a given service method - e.g. it is the case for Auth and TimeStamp standard method services.

Feedback is welcome on our forum, as usual.