1. REST authentication methods
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 TSQLite3HttpClientWinHTTP
and
TSQLite3HttpClientWinINet
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. In our framework, since we don't provide HTTP-like headers within each request, we can not handle cookies natively, for all transmission protocol used. So we tried not to use the Cookie technique.
Query Authentication
Query Authentication consists in signing each RESTful request via
some additional parameters on the URI.
See
this reference 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 URL encoding the query string.
For instance, here is a generic URI sample, extracted from the link above:
GET /object?apiKey=Qwerty2010should 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.
2. SQLite3 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 (for instance for local
access of data);
- With per-user authentication and right management via using defined
security groups, and a per-query authentication.
According to RESTful principle, handling per-session data is not to be implemented in such an 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-URL signature, over a light per-User session authentication.
Per-User authentication
On the Server side, a dedicated RESTful Service, accessible
via the ModelRoot/Auth
URI is to be called to register an User,
and create a session. On the Server side, two tables, implemented by
TSQLAuthGroup
and TSQLAuthUser
will handle
respectively per-group access rights, and user authentication.
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. For security reasons,
the ability to execute INSERT / UPDATE / DELETE SQL statement via a RESTful
POST command is never allowed with remote connections: only SELECT can be
executed via this command.
If authentication is enabled for the Client-Server process (i.e. if both
AuthGroup
and AuthUser
are available in the Server
TSQLModel
, i.e. aHandleUserAuthentication=true),
each REST request will expect an additional parameter, named
session_signature
, to every URL. Using the URL instead of cookies
allows the signature process to work with all communication protocols, not only
HTTP.
This will implement both Query Authentication together with a group-oriented 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.
Here are the typical steps to be followed in order to create a new user
session:
- Client sends a GET ModelRoot/auth?UserName=...
request to the
remote server;
- Server answers with an hexadecimal nonce contents (valid for about 5
minutes);
- 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 failed;
- On success, Server will create a new in-memory session (sessions are not
stored in the database, for lighter and safer process) and returns the session
number and a private key to be used during the session;
- 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;
- When the Client is about to close (typically in TSQLRestClientURI.
Destroy
), the 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);
- 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.
Note that with this design, it's up to the Client to react to an authentication error during, and ask for the User pseudo and password at any time. For multiple reasons (server restart, session timeout...) the session can be closed at any time by the Server.
URI signature
Query Authentication is handled at the Client side in TSQLRestClientURI. SessionSign method, by computing the session_signature parameter for a given URL.
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.
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
either from a AJAX / JavaScript client.
More details are available in the SAD document of the framework official
documentation.
Feedback and comments are welcome on our
forum.