Let's say that I'm considering designing a WCF service whose primary purpose is to provide broad services that can be used by three disparate applications: a public-facing Web site, an internal Windows Forms application, and a wireless mobile device. The purpose of the service is twofold: (1) to consolidate code related to business processes in a central location and (2) to lock down access to the legacy database, finally and once and for all hiding it behind one suite of services.
Currently, each of the three applications has its own persistence and domain layers with slightly different views of the same database. Instead of all three applications talking to the database, they would talk to the WCF service, enabling new features from some clients (the mobile picker can't currently trigger processes to send e-mail, obviously) and centralizing notification systems (instead of a scheduled task polling the database every five minutes for new orders, just ping the overhead paging system when the AcceptNewOrder()
service method is invoked by one of these clients). All in all, this sounds pretty sane so far.
In terms of overall design, however, I'm stumped when it comes to security. The Windows Forms application currently just uses Windows principals; employees are stored in Active Directory, and upon application startup, they can login as the current Windows user (in which case no password is required) or they can supply their domain name and password. The mobile client doesn't have any concept of a user; its connection to the database is a hardcoded string. And the Web site has thousands of users stored in the legacy database. So how do I implement the identity model and configure the WCF endpoints to deal with this?
In terms of the Windows Forms application, this is no great issue: the WCF proxy can be initiated once and can hang around in memory, so I only need the client credentials once (and can prompt for them again if the proxy ever faults). The mobile client can just be special cased and use an X509 certificate for authentication against the WCF service. But what do I do about the Web site?
In the Web site's case, anonymous access to some services is allowed. And for the services that require authentication in the hypothetical "Customer" role, I obviously don't want to have to authenticate them on each and every request for two reasons:
- I need their username and password each time. Storing this pair of information pretty much anywhere--the session, an encrypted cookie, the moon--seems like a bad idea.
- I would have to hit the users table in the database for each request. Ouch.
The only solution that I can come up with is to treat the Web site as a trusted subsystem. The WCF service expects a particular X509 certificate from the Web site. The Web site, using Forms Authentication internally (which invokes an AuthenticateCustomer()
method on the service that returns a boolean result), can add an additional claim to the list of credentials, something like "[email protected] is logged in as a customer." Then somehow a custom IIdentity object and IPrincipal could be constructed on the service with that claim, the WCF service being confident that the Web site has properly authenticated the customer (it will know that the claim hasn't been tampered with, at least, because it'll know the Web site's certificate ahead of time).
With all of that in place, the WCF service code would be able to say things like [PrincipalPermission.Demand(Role=MyRoles.Customer)]
or [PrincipalPermission.Demand(Role=MyRoles.Manager)]
, and the Thread.CurrentPrincipal
would have something that represented a user (an e-mail address for a customer or a distinguished name for an employee, both of them useful for logging and auditing).
In other words, two different endpoints would exist for each service: one that accepted well-known client X509 certificates (for the mobile devices and the Web site), and one that accept Windows users (for the employees).
Sorry this is so long. So the question is: Does any of this make sense? Does the proposed solution make sense? And am I making this too complicated?