I had an abortive IM conversation with Benjamin stemming from this post over on Das Blonde. I was multi-tasking at work and he was at home typing one-handed with a baby on his knee, so our conversation didn’t get very far. So this is an attempt to put down a bunch of thoughts I’m having about per-user security and Web Services. (Note: now that I’ve written this piece, I realize that it ended up having very little to do with what I originally intended it to be about. I’ll have to come back to Michelle’s post later).
The Scenario
Let’s say that I have an ASP.NET front-end to a payroll system. This consists of a set of web pages that let users view and modify company payroll information. On the back end, there’s a set of payroll web services that provides data to a number of different applications – my web app happens to be one of these. Bob is an employee at my company; he should be able to use the web app to view his information, but should not be able to see anyone else’s. Alice is the HR person for my company; she can look at anybody’s information using my web app. Finally, as an added level of security, only trusted applications should be able to send messages to the Payroll web services.
I think these requirements are representative of common business requirements. So, what’s the best way to implement the simultaneous requirements of user-level and system-level? How does role-based security play into the solution?
Problem 1: Where are the authoritative credentials stored?
Authorization is the process of taking some identity claims from the user (e.g. a username/password combination) and verifying those claims against some authoritative credentials. In my scenario, there are three possible places to store those credentials.
Option A: Store the credentials on the client web app
In this option, each client application would maintain its own credentials database. Thus, when Alice logs in to my web app, the first thing I do is check her identity claim against her username/password information that I’ve stored locally. Once I’ve verified Alice’s identity, I can send messages to the Payroll web service to the effect of “I am the PayrollWebClient system acting on behalf of Alice (trust me, she’s really Alice!), and she’d like to see her last paystub”. The payroll service trusts the web client to make sure that’s really Alice sitting at the keyboard, and fulfills the request.
Implementation-wise, this could be accomplished by issuing X509 certificates to every trusted system that’s allowed to call the Payroll service. All messages sent to the Payroll web service would need to be signed and encrypted with one of these trusted certificates, and we’d assert this requirement via policy. This would be sufficient to establish system-level trust. The user context for a given SOAP message could just be conveyed in a SOAP header; since the service trusts the app sending the message, the correctness of this user-level information can just be taken on faith (sidenote: what would this user identity information look like, exactly? A username token with no password? Something else?)
The downside to this is that identities are now scoped to the client application, so if Alice and Bob need to use more than one application they need to remember more than one username and password. Usability degrades and application management becomes difficult.
Option B: Store the credentials with the service
In this option, the client application is responsible for collecting identity claims but does not validate them. Instead, it passes those claims on to the service in every subsequent message sent on that users behalf. System-level trust is handled in the same way as Option A. In effect, messages sent using this options say “I am the Payroll WebClient acting on behalf of a user who claims to be Alice; if she’s really Alice, she’d like to see her last paystub.”
This option seems the easiest to implement in WSE2. My client application is now responsible for keeping track of which claims belong to which user, and appending those claims to every envelope I send on behalf of that user. I might choose to accomplish this by creating a UsernameToken based upon what they entered in the login box, and then appending that to each envelope via SoapContext.Current.Security.Tokens. I’m pretty sure you could set up a policy to assert the presence of both an X509 cert for system-level authentication and a UsernameToken for user-level authentication. Alternatively, I could take advantage of Secure Conversation and implement a custom SecurityTokenManager on my web app, so I didn’t need to reauthenticate at the server side on each request (but this would basically just be an optimization, not a fundamental change to the security architecture).
The downside: It’s possible that Alice still has multiple identities. What happens if my client app needs to invoke two different services in two different security realms? I now have to maintain multiple set of identity claims for Alice. Worse yet, Alice has to provide me all of those identity claims, meaning she has to enter multiple username/passwords if her credentials are not identical in both security realms. Again, management and usability become problematic.
Option C: Store the credentials with a trusted 3rd party authentication service
In this option, all services agree to federate identity with a trusted third party. When Alice logs in, my web app passes her identity claim to the authentication service, which validates her identity and issues back some token. By including this token on all subsequent messages my system has can say “I am the PayrollWebClient, acting on behalf Alice (as verified by someone we both trust). She’d like to see her paystub.” This seems like it would solve all our management problems; Alice has only one authoritative identity and everyone agrees to use that. Definitely more manageable than A or B.
The downside: realistically, this is only feasible in the context of Secure Conversation. Otherwise, you double the number of service calls (every message must be first sent to the authenticator, and then to the service). Secure Conversation means that you only need to authenticate once to establish security context. Unfortunately, support for 3rd-party token issues was removed at the last minute from WSE2. One possible option would be to roll your own SCT, but in doing so you risk compromising interoperability down the road.
Problem 2: Now that we know who Alice is, what can she do?
Identity verification is only one piece of the puzzle. It’s all well and good to know that Alice is in fact who she says she is, but that information is almost useless unless you have some idea of what her “Aliceness” implies. In my scenario, Alice is an administrator with special privileges. This is a classic role-based security problem which becomes more complicated in the web services world. Role-based security boils down to two sub-problems: membership and enforcement. Membership information is usually stored with identity information, while enforcement should be done as close to the code subject to the security restrictions as possible. Given these requirements, it makes sense to look again at the three options for storing identity:
Option A: Credentials-on-client
This option really starts to look ugly when you take role-based security into account. Since identity (and now role) information is scoped to the client application, you get this proliferation of roles and identities. Does one client’s concept of “admin” mean the same thing as another client’s concept of “admin?” Enforcing role-based security at the service is now very difficult, because the service needs to know about all the applications that might invoke it (so it can know about all the possible roles they might define). The only really manageable option would be for each client to enforce security restrictions, cutting off access to a restricted service before the message is ever sent. Problem is, now you’ve embed what is effectively a service-level rule into each client that calls it. What happens when those security requirements change? Doesn’t feel right to me.
Option B: Credentials-on-service
This option is definitely more appealing than Option A, because the service owns the roles as well as the identity information. With this option, the service can guarantee that only one authoritative “admin” role exists (at least, as far as it’s concerned – if a client needs to call two different services as “admin”, there’s still the multiple-identity problem as above). Thus, the service can enforce its own security rules and role enforcement is done at the service instead of the client, which is IMHO how it should be done. The downside here is that the client has no longer has any information about what a user can or cannot do. This leads to a usability problem – say you have some secured form that takes 4 screens to fill out. If you knew up front that the client didn’t have the security clearance to submit the form, you could spare your user the pain of filling out a form that has no hope of being filled out correctly. However, you can’t take that “up front” action because you don’t know who that user actually is until they send a message to the service.
Option C: Trusted 3rd party authenticator
Again, this seems like the best of the three. Centralize all identity and role information inside of a trusted third party, and let that 3rd party be the authoritative source for identity verification and role membership. No more multiple roles, meaning that security enforcement can be localized to the service, where it belongs. Furthermore, if SCT’s are used, the client could look inside the issued SCT for role information, allowing role-based security enforcement at both the client and the service if needed. It seems like a federated identity system would bring all of the conceptual goodness of Active Directory, only “servicified”.
And now for the downside…as I said before, there’s no out-of-the box mechanism for issuing 3rd party SCTs using WSE2. Furthermore, I don’t think there’s any standardized way of exchanging role information in the context of an SCT – SAML and WS-Federation are still a ways out. Rolling your own is an option, but with the associated interoperability risk. However, we’re still way better off now than we were with WSE1. At least with WSE2, we’re only fighting about wire format – we’re still figuring out what the best way to convey role information inside of an SCT is, but at least we have the infrastructure in place to issue, renew, exchange and validate those SCT’s already done for us.
Conclusion
WSE2 is a big step forward in term of secure web services, but it’s not the endgame just yet. There’s a lot that can be done with WSE2 out of the box directly. Integrating with Windows domain authentication is a big step; it solves one part of the role-based security puzzle. If all your services can talk to the same NT credential store, you can auto-issue SCT’s to your heart’s content and at least have what I describe as “Option B” taken care of (with the added benefit of having a unified cross-service credential store). It’s not the general-case, interoperable solution that pan-enterprise web services need to succeed, but it’s a step in the right direction. The real solution lies in federating identities, and that’s something for which we’ll just have to wait.
