Web Services and Propagating User Identity
Oftentimes, business critical web service interactions happen within the context of a user. User interactions with a web app might initiate communication (either synchronously or asynchronously) with web services deployed elsewhere. In such cases, how does your app let the web service know who the user is, so that the web service can impose security constraints on the kinds of transactions that can be done, or the kinds of data that can be accessed?
For example, think of a web service that provides some business reports, that can be accessed via a finance portal. A user "joe" logs into the portal, and wants to access some reports. He then fills in a form to view the reports. Let us assume that the report generating web service has some access control mechanism on the kinds of reports that a user like can "joe" can access. How to let the report generating web service know that a user "joe" , and not "superjoe" is making the request for some reports?
The essence of the problem is propagating the identity of user from a client to a web service. This was one of the key questions we had to address when we started implementing WSRP almost two years ago. In our case, both the client and service were deployed on J2EE servlet containers. The client was a portal running on the servlet container, and the web service was also built on top of a servlet container. The web service invoked servlets, JSPs, portlets etc. So, we had a web service request sent from one web container to another container. On each side, you could ascertain the identity of the user with the java.security.Principal associated with the javax.servlet.http.HttpServletRequest. So, the question was propagating the identity of the user from the client to the service such that the container hosting the service can rely on the java.security.Principal and impose security constraints on the user.
When we started working on this problem, there were no standard solutions. But today, there are some agreed-upon ways to solve this problem, at least at the web services layer, and it is possible to guarantee interoperability. In this post, I would like to discuss why web services applications should rely on these instead of non-interoperable proprietary techniques.
User Identity within SOAP Requests
Some people attempt to send the name of the user as part of the each SOAP request. Here is an example.
<xs:element name="ReportRequest">
<xs:complexType>
<xs:sequence>
...
<xs:element name="username" />
</xs:sequence>
</xs:complexType>
</xs:element>
However, I have a few arguments against doing that:
- In principle, details relating to security should best be transmitted at the SOAP level or even at the network transport level, and not at the application level. Compare this to adding a username argument to each method on EJBs. This is rarely done. Instead, we would let the user authenticate with the servlet container (e.g. using basic or form-based authentication), and then let the container propagate the identity of the user under the wraps to the EJB container. In our case, instead of the application, the SOAP toolkit or runtime on the client side should add the security details to SOAP messages (e.g. as SOAP headers).
- Sending just the name of the user is rarely enough. Any client can send some arbitrary SOAP request with some fake username. Therefore we also need to worry about establishing trust, encryption, XML signatures etc, so that the web service can trust and process the identity information received without worrying about spoofing, replay attacks etc. All these details can best be addressed by the SOAP runtime/toolkit being used, so that applications need not introduce additional complexity into their code.
- Interoperability is another reason why applications should not deal with user identity and other security issues directly.
So, it is better to stay away from this approach.
How else to solve this problem? There are three problems to be solved here - (a) establish some form of trust between the client and the service, (b) propagate the identity of the user, and (c) reestablish the identity on the service side.
Establish Trust
Why is trust important here? Any client could send any set of credentials or a secure token, and not every client may be trust-worthy. You can argue that only "trusted" clients can be allowed to connect to the service. We can use a firewall to establish this. However, this may be not be enough for trust in all cases. Firewalls provide a coarse-grained guard against unauthorized access.
One of the common ways of establishing trust between applications is to rely on the public-key infrastructure. We can let the receiving application (the web service) accept a X.509 certificate of the sender out of band. By accepting the certificate of the sender, the receiver can assert that it trusts any data signed by using the private key of the sender. If the recipient can verify the signature, it can conclude that the data came from a trusted source, if not, the sender is untrusted.
There is also an initiative to formalize a Web Services Trust (WS-Trust) language to specify means for establishing trust.
Propagate Identity
There are a few ways to propagate the identity:
- Let the client send the credentials (e.g. user name and password) of the user to the service. The service can use the credentials to explicitly authenticate the user.
- Let the client send a token to the service. The token asserts that the user is so and so.
In the first approach, the client acts as a credential store for users. In the second approach, the client asserts the identity of the user. With the second approach, there is no need to manage credentials securely of all users. It also avoids the need to send those credentials across the wire, however secure the communication channel may be.
You can implement the first approach using the Username Token Profile. For example, the sender can include the following SOAP header to send the credentials of the user.
<soapenv:Header>
<wss:Security xmlns:wss="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wss:UsernameToken>
<wss:Username wsu:Id="someId"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">joe</wss:Username>
<wss:Password Type="...#PasswordText">password</wss:Password>
</wss:UsernameToken>
</wss:UsernameToken>
</soapenv:Header>
</wss:Security>
The Password element need not necessarily be the actual clear text password used for authentication. It could as well be a hash of the password. As a precaution against replay attacks, the sender can also include a Nonce and time of creation of the token. The sender can also digitally sign the UsernameToken, so that the recipient can verify the signature before processing the contents of the token.
The SAML Token Profile can be used to implement the second approach. In this approach, the sender sends a SAML IdentityAssertion to assert the identity of the user. The IdentityAssertion is an assertion statement made by the sender. The recipient may choose to trust that assertion and treat the user authenticated.
<soapenv:Header>
<urn:Assertion MinorVersion="1" MajorVersion="1" Issuer="..."
IssueInstant="2004-12-27T08:25:43.609-07:00"
AssertionID="..."
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:urn="urn:oasis:names:tc:SAML:1.0:assertion">
<urn:AuthenticationStatement AuthenticationInstant="2004-12-27T08:25:43.609-07:00"
AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password">
<urn:Subject>
<urn:NameIdentifier NameQualifier="..."
Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">joe</urn:NameIdentifier>
</urn:Subject>
<urn:SubjectLocality DNSAddress="mydomain"/>
</urn:AuthenticationStatement>
<dsig:Signature>
...
</dsig:Signature>
</urn:Assertion>
</soapenv:Header>
Authenticating the User
Now that the identity is propagated, the final question is letting the recipient authenticate the user. It is tempting to extract the identity from the Username or IdentityAssertion token, and pass it directly to various APIs available to the web service implementation. In our case, portlets use the servlet API (or the portlet API), and it is possible to override the javax.servlet.http.HttpServletRequest.getUserPrincipal() to return a java.security.Principal based on the token. This is tempting, but is not the right thing to do. Here is why.
In most J2EE runtimes, the identity of the user is managed via some form of security context. This context is associated with the current thread. Unless the security context is correctly established, none of the container-managed security checks will function as expected. Overriding certain APIs is not enough.
Therefore, the problem is to process the Username token or IdentityAssertion, and establish the security context. Unfortunately, there are no standard or cross-platform APIs to accomplish this. In our case, WebLogic Server has the notion of Identity Asserters that can be used in conjunction with some security APIs to assert the identity of the user based on any arbitrary token. Other servers may have similar APIs.
The point is that, in 2004, there are agreed-upon ways to propagate the identity of the user across web services. There is no reason to "hack" security within web service applications.



Thanks for this post … 4 years later it is still relevant and useful