SAML v2.0 vs. JWT: SAML2 with SOAP Web Services and REST APIs
This post continues our look at SAML v2.0 Use Cases. The first two use cases are described in “SAML v2.0 vs JWT: SAML2 Web Application SSO Use Cases”. The full list of SAML2 vs JWT-related blog posts can be found here.
Use Case #3: SOAP Web Service & SAML 2.0
This use case describes how to authenticate a SOAP Service Consumer to a SOAP Service Provider using SAML2, WS-Trust, and WS-Security. Another way of thinking about this use case is that it is how to convey an identity from a SOAP Service Consumer to a SOAP Service Provider using SAML 2.0 in a secure fashion without revealing the user credentials (password, for example) to the Service Provider.
The SAML Token used in this scenario is mostly similar to that described here. So, we won’t go through a detailed description, again. The Audience information in the token will be relevant to the SOAP Service Provider. Now, where things become interesting is what value the audience contains. It will be in a URL form. But, is it the URL of the SOAP Service? That is a common approach. Or, should it be something more granular? Such as a representation of the operation? This could be done with something like
or
If there are many SOAP Web Services using SAML for authentication, having a unique Relying Party (audience) for each SOAP service (let alone for each SOAP operation) could become prohibitively complex to manage — especically, for an STS that wasn’t constructed with this use case in mind. Of course, if you have mastered the whole DevOps, self-service/automation thing (and that dicipline extends to your Identity Stack), then complexity and overhead of managing 100s or 1000s of RPs within your Federation Server (Identity Provider or STS) will not pose a significant challenge. On the other hand, if all Identity and Access Management (IAM) processes are done manually, it is an near absolute certainty this will be very challenging (if not impossible). Other possible approaches include having a common RP used across all services advertised in an environment, gateway, or ESB — this common RP could be the ESB host or a meaningful string. This isn’t a particularly secure approach, but it has been done before; it isn’t ideal because you are intentionally increasing the number of legitimate places a compromised bearer token could be used. With a legitimate understanding of risks and appropriate mitigation, this could be viable approach.
The WS-Trust v1.3 specification describes a standard way for SOAP system actors to interact with a Security Token Service (STS) via a SOAP-based protocol. An STS is a WS-Trust term for an Identity Provider that can issue, validate, renew, and invalidate tokens. Like SAML2, there are numerous use cases and the design decisions surrounding optional components of the WS-Trust v1.3 spec. Chapter 2 of the spec outlines the trust model (what authentication methods are supported) that the STS must support — it is very high-level. From the spec, “Authentication of requests is based on a combination of optional network and transport-provided security and information (claims) proven in the message. Requestors can authenticate recipients using network and transport-provided security, claims proven in messages, and encryption of the request using a key known to the recipient.” Just about every STS in existance at least supports Basic Authentication. Most STS vendors support far more authentication methods than this; coming up with a common authentication method supported by WS-Trust clients and the STS can become complicated when dealing with a multi-vendor/multi-platform environment.
WS-Trust v1.4 is more recent, but my functioning examples use WS-Trust v1.3. A WS-Trust client must create a RequestSecurityToken message that is wrapped in a SOAP request that will look something like:
<s:Envelope
xmlns:u=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:a=”http://www.w3.org/2005/08/addressing"
xmlns:s=”http://www.w3.org/2003/05/soap-envelope"
xmlns:trust=”http://docs.oasis-open.org/ws-sx/ws-trust/200512"
xmlns:wsp=”http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wsa=”http://www.w3.org/2005/08/addressing"
xmlns:wsu=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<s:Header>
<a:Action s:mustUnderstand=”1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
<a:MessageID>urn:uuid:827459874359082587924598238572094852485</a:MessageID>
<a:ReplyTo> <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand=”1">https://idp.levvel.io/sts/wstrust14</a:To>
<wsse:Security>
<wsse:UsernameToken wsu:Id=”unt-001">
<wsse:Username>user</wsse:Username>
<wsse:Password Type=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</s:Header>
<s:Body>
<trust:RequestSecurityToken>
<wsp:AppliesTo>
<wsa:EndpointReference> <wsa:Address>https://api.levvel.io/serviceA</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
<trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
<trust:TokenType>urn:oasis:names:tc:SAML:2.0:assertion</trust:TokenType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>
The WS-Trust specification uses numerous other WS-* specs to accomplish its goals — including WS-Addressing, WS-Policy, WS-SecurityPolicy, WS-Security, WS-Security Username Token Profile. This message uses a Username Token defined by the WS-Security Username Token Profile v1.0 spec to authenticate the Principal. The message uses WS-Addressing to describe the Action, MessageID, and ReplyTo, and To elements in the SOAP Headers. The Action element describes the WS-Trust RequestSecurityToken (RST) Operation that should be used by the STS — in this case, RST Issue operation. The MessageID element provides an identifier for this message. The ReplyTo element contains an Address element that defines the URL that the receiver should reply to — this is not used here. The To element defines the IdP URL that the WS-Trust RST Issue message is being sent to. The Security header is defined by the WS-Security specification. The Security header contains a UsernameToken element. The UsernameToken element (as the name implies) is referred to as a Username Token (or UNT); this contains a userid and password that would be validated by comparison to a User Repository (such as an LDAP server). The SOAP Message body contains the RequestSecurityToken element. This is the top-level element of the WS-Trust request message. It contains AppliesTo, KeyType, RequestType, and TokenType elements. The AppliesTo address contains a WS-Addressing Address element that contains the URL that will be used as the Audience in the resulting SAML Assertion. The KeyType element contains the WS-Trust identifier for a Bearer token. The RequestType element references the WS-Trust operation name (Issue in this case) URL. The TokenType references the SAML Assertion identifier.
If the credentials, AppliesTo Address, and other parameters in the WS-Trust RST Issue message are valid, the STS will respond with a RequestSecurityTokenResponseCollection Message similar to the following:
<soapenv:Envelope
xmlns:wsa="http://www.w3.org/2005/08/addressing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<wsa:Action soapenv:mustUnderstand="0">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</wsa:Action>
<wsa:MessageID soapenv:mustUnderstand="0">urn:uuid:38450984389023809258904580945</wsa:MessageID>
<wsa:RelatesTo soapenv:mustUnderstand="0">urn:uuid:89439034834985334983048543098</wsa:RelatesTo>
<ns1:Security soapenv:mustUnderstand="0"
xmlns:ns1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<ns2:Timestamp
xmlns:ns2="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<ns2:Created>2016-05-14T00:10:000Z</ns2:Created>
</ns2:Timestamp>
</ns1:Security>
</soapenv:Header>
<soapenv:Body>
<wst:RequestSecurityTokenResponseCollection
xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
<wst:RequestSecurityTokenResponse wsu:Id="_892458934508345083548035980345"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wst:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</wst:TokenType>
<wsp:AppliesTo
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsa:EndpointReference>
<wsa:Address>https://api.levvel.io/serviceA</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<wst:Lifetime>
<wsu:Created>2016-05-14T00:10:00.000Z</wsu:Created>
<wsu:Expires>2016-05-14T00:15:00.000Z</wsu:Expires>
</wst:Lifetime>
<wst:RequestedSecurityToken>
[Omitted for brevity. Details are here.]
</wst:RequestedSecurityToken>
<wst:RequestedAttachedReference
xmlns:wss="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wss:SecurityTokenReference wss11:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"
xmlns:wss11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd">
<wss:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">498098435934893450983459324</wss:KeyIdentifier>
</wss:SecurityTokenReference>
</wst:RequestedAttachedReference>
</wst:RequestSecurityTokenResponse>
</wst:RequestSecurityTokenResponseCollection>
</soapenv:Body>
</soapenv:Envelope>
The WS-Trust Response Message shown above contains several SOAP Headers: Action, MessageID, RelatesTo, and Security. The Action element provides a processing context to the recepient. The MessageID is a unique identifier for this message. The RelatesTo field ties this response back to a request message identifier. The Security header is the standard WS-Security SOAP Header; in this case, it contains a time stamp. The message body contains a RequestSecurityTokenResponseCollection, which in turn contains a single RequestSecurityTokenResponse element. This element contains TokenType, AppliesTo, Lifetime, and RequestedSecurityToken elements. The TokenType element defines what type of token the response contains — in this case a SAML2 token. The AppliesTo element contains the EndpointAddress whose value is the request message’s AppliesTo value. The Lifetime element defines the creation and expiration dates (Created and Expires elements). The RequestedSecurityToken element contains exactly what its name would imply — the requested SAML Assertion. The SAML Assertion is more or less exactly the same as the tokens used in Use Case #1 and Use Case #2 — the only difference is the Audience information.
The SAML Assertion in the WS-Trust response message is extracted by the SOAP Service Consumer and placed into the SOAP Request Message WS-Security Security Header similar to:
<soapenv:Envelope
xmlns:soapenv=”http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws=”http://ws.rcbj.com/">
<soapenv:Header>
<wsse:Security
xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand=”1">
<saml:Assertion>
[Omitted for brevity. Details here.]
</saml:Assertion>
</wsse:Security>
</soapenv:Header>
<soapenv:Body wsu:Id=”_2">
<ws:sumRequest>
<return>9</return>
</ws:sumRequest>
</soapenv:Body>
</soapenv:Envelope>
This SOAP Request without the SAML Assertion is from an earlier blog post on ThinkMiddleware.com. The WS-Security 1.0 SAML Token Profile describes how to put a SAML 2.0 Assertion into the WS-Security SOAP Header (Security element) of a SOAP message. To add an additional layer of assurance that this SAML Assertion is not reused in undesired ways, a digital signature could be added over the SOAP Message Body and/or SOAP Headers of each request message being sent with the SAML Assertion. The signing key would belong to the SOAP Consumer (application most likely) rather than an end user identity. A similar level of assurance could also be achieved with mutually authenticated SSL and a mapping of client certificates to particular application identities.
Putting all of this together, we have the following diagram:
Depending on the token lifetime and reuse policies (for the organization, system, environment), there may be performance advantages to caching the Assertion(s) on the Service Consumer. Whether or not there is an advantage depends on how often the same Principal (user, session, security context, etc) is making calls to the same SOAP Service. Or, at least, to a SOAP Service with an identical Audience. It’s also possible to have multiple Audience elements listed; this can become rather complicated. Allowing tokens to be cached on a system actor that is acting as a Service Consumer should only be done if that actor can be properly secured. What constitutes properly secured is beyond the scope of this blog post. It is common for JEE Application Servers (WebSphere Application Server or JBoss EAP, for example) to store the SAML Assertion in the SecurityContext (JAAS Subject).
The validation of the SAML Assertion (per the SAML 2.0 spec) by the Service Provider can be done without a call back to the IdP.
The SOAP response message does not contain an identity token of any kind and is not particularly important to this example. So, it isn’t given here.
Use Case #4: REST API & SAML2
The first three SAML 2.0 use cases in this series were all clearly defined by the various specifications that we have discussed. This one is a bit more gray — in line with the lack of formal standards around EEST and APIs. To add some structure to this discussion, recall the four Web Application Architectures that were discussed in the API Security vs. Web Application Security Part 1: A Brief History of Web Application Architecture. We will go through each of these architectures and explore how SAML could be used for authentication and identity propagation.
In Web Application Architecture #1, there isn’t really an API to speak of. But, for completeness, either Use Case #1 or #2 from above would apply.
In Web Application Architecture #2, there is a concept of an API. All of the API calls are part of a security session that is tracked on the web server. Here too, Use Case #1 or Use Case #2 could be used. However, the SAML Assertion is only used at the beginning of the session to establish it. After that, the security session is tracked through a session cookie or similar mechanism as described in the API Security vs. Web Application Security series.
In Web Application Architecture #3, a separate security session must be established with each API Provider.
- This could be done with Use Case #1 (Service Provider Initiated Web App SSO) for each individual API Provider that the web application interacts with. Once again, the security session is tracked through a session cookie or similar mechanism like our last example.
- Another approach would be to have the Web Application use Use Case #1 to obtain a SAML Assertion for each API Provider, cache those Assertions, and use them as necessary to interact with API Providers. A single token could also be used that contains multiple audience conditions. The difference between this and the last option is that the SAML Assertion is included with each call made to the API Provider(s). However, this is jumping ahead in my original taxonomy of Web Application Architectures — bearer tokens weren’t being included with every call.
In Web Application Architecture #4, something similar to the second option for Web Application Architecture #3 could be used.
Now, how does the API Consumer send the SAML Assertion to the API Provider? RFC 6750 defines how to place a bearer token into the Authorization HTTP header, form-encoded body parameter, or query parameter — the Authorization header approach is probably the most common. The base64-encoded Assertion would be used in any of those locations.
If the API request contains a message body that is XML, then the Assertion could be embedded there. The base64-encoded Assertion could also be stored in a cookie. Likewise, the base64-encoded value could be kept in any arbitrary HTTP Header, Query Parameter, or form-encoded body parameter. Of course, none of these options would be a spec-defined approach to security, which is the goal.
In each of these scenarios, API Consumer would obtain the SAML Assertion through SAML2 Browser Profile, WS-Trust call, or a similar mechanism. The same concerns described in Use Case #3 would apply here.
Similarly, the API Provider must validate the SAML Assertion per the SAML2 specs in the same manner as in Use Case #3.
In the next post, we will look at the final SAML2 Use Cases.