How can I obtain the authenticated Username and Password in a Datasnap Server Methods unit?
Datasnap authentication is pretty straightforward once you use the correct parameter names (explained in Delphi Datasnap Server User Authentication). The next problem is to be able to use those same credentials when using a FireDAC Database Connection. The answer seems to be implied in Most efficient way to pass SQL Login credentials to Delphi Datasnap servers? although "simply forwarded" doesn't really explain how to accomplish the code. Further, these credentials should be authenticated as the same credentials used to log on to the Datasnap server. This will prevent impersonation at the database level.
So far I haven't been able to discover any way to obtain the current Datasnap user credentials programmatically from within the Server Methods unit. For example, in a BeforeConnect event. The code I'm working on is a standalone server built with Delphi XE7 using a Session lifecycle.
Here is a description of the events that take place when connecting to a Datasnap server and requesting data:
Let me explain further:
By using a few ShowMessage instructions I can trace the flow of Datasnap as I connect and make a data request. Running the server under Test allows me to display the contents of the various parameters that accompany the events. Once set up, running a client that connects to the server and requests data results in the following:
Client Login Button
- Credential Dialogue presented (this is my code, not the property of TSQLConnection that gets it wrong.)
- Username and Password entered.
- Paremeters Placed in TSQLConnection.Params [DSAuthenticationPassword, DSAuthenticationUser]
- Set TSQLConnection.Connected := True
Server event OnUserAuthenticate
At this point, the connection is established although nothing else has taken place on the server. Most importantly, neither the ServerClass nor the ServerMethodsClass have been instantiated. There is no place that I can see to store authentication credentials.
Client GetData Button
- Client opens two client datasets consecutively.
- Server DSServerClass1.Create. This apparently implies that the ServerTransport recognizes the request as valid and proceeds to create the necessary assets to handle the session.
- ServerMethodsUnit3.Create. Somewhere buried in the DSServerClass there is a GetClass invocation that returns the name of the associated ServerMethodsClass. The named class is instantiated. Since the ServerMethodsClass ultimately descends from a TDataModule this implies property streaming as well.
- OnUserAuthorize (Apparently first dataset)
- OnUserAuthorize (Apparently second dataset)
Data is returned to client (At this time the database credentials are hard coded into the TFDConnection ConnectionDefinition just to get it to work.)
OnUserAuthorize presents Sender parameter with a nil value; also presented is a TDSAuthorizeEventObject that doesn't contain any references that I have been able to use to find the ServerMethods instance, and, finally, a parameter named Valid, a boolean value used to authorize the user.
Note that TDSAuthorizeEventObject contains a reference to TDSServerMethodUserEventObject that DOES contain the Username along with Roles, Authorized Roles and Denied Roles. However, so what? This brings me back to my original question: How do I communicate this to code in the ServerMethodsUnit?
After some time I stumbled on the exact methodology to do this in a white paper by Bob Swart. There are several methods that will enable you to persist information during a Datasnap session. These belong to TDSSessionManager.GetThreadSession and are GetData, PutData, RemoveData, HasData, GetObject, PutObject, RemoveObject and HasObject. These methods actually manage two dictionaries that are part of the Session. So for example, in the UserAuthentication event you might store the username as follows:
Later, in a OnBeforeConnect event for a database connection, you can retrieve these values for use in the connection:
DBUserName := TDSessionManager.GetThreadSession.GetData('entrykey');
In practice I find the "object" forms of greater value but either form offers a powerful way to persist your own data for the life of a session without resorting to external media.
In your linked question, the TServerContainer1.DSAuthenticationManager1UserAuthenticate method has User and Password parameters. Your server side code can retrieve the credentials from the request parameters, validate them, and re-use them for authentication with the database.
procedure TServerContainer1.DSAuthenticationManager1UserAuthenticate( Sender: TObject; const Protocol, Context, User, Password: string; var valid: Boolean; UserRoles: TStrings); if UserService.isUserValid(User, Password) then begin // use User and Password ... end;