SAML Authentication for Web Applications

Posted on July 16, 2010 by Tommy McGuire
Labels: SAML, authentication, protocols, http, OpenAM, java
Since last fall, I have written a number of posts about various ways of authenticating web application users, such as Active Directory and SPNEGO/Kerberos. Hopefully, this and any immediate follow-ons will be the last of them for a while. And I really mean that, because I am tired of the churn. (Although the Active Directory stuff is actually being used at the moment. Yay!)

This post is intended to document the basic process of authenticating web application users using SAML, the Security Assertion Markup Language, and the related protocols, bindings, and what-not.

This whole process started because my organization is using Sun Access Manager (SAM), a.k.a. OpenSSO, a.k.a. OpenAM. In our case, OpenSSO had a Policy Agent, in the form of an Apache httpd module, which squatted in front of the web application server and validated requests before the application saw them. It works by redirecting the user's browser to a OpenAM server which performs the authentication and redirects the browser back to the application in the case of success. By "works", I mean only for GET and POST requests, periodically mangling the requests it does pass, and occasionally locking up httpd in the face of BlazeDS traffic. Couple that with some serious teething problems with the SAM servers and the general performance of something like 11 redirects and automated POSTs, and some extra work to investigate alternatives sounds good. In any case, the alternatives to Sun Access Manager/OpenSSO/OpenAM have been rejected for various non-technical reasons.

SAML


The current iteration involves integrating with SAM via SAML2, the Security Assertion Markup Language, version 2. The integration is performed by a Java Servlet filter and some other components written by yours truly, rather than an off-the-shelf policy agent written by crazy people. [Ed: Other crazy people.]

There are, roughly speaking, five parts to my SAML2 authentication system. One is the user's web browser, identified as a "User Agent" (UA) below. Another is the SAM authentication server or servers, collectively identified by the SAML term Identity Provider (IdP) below. Because the actual authentication process is completely undetermined, the actual make-up of the IdP is somewhat fuzzy.

The remaining three parts make up what SAML would call a Service Provider (SP), I believe. The most important part is the web application itself, App. For our case, the App is a traditional Java web application. A Java servlet Filter sits in front of part or all of the App's URL space, blocking unauthenticated access and triggering the authentication process. Finally, there is the SAML2 Assertion Consumer Service (ACS). The response to the SAML2 authentication request, which is sent to the ACS, is one or more assertions about the user being authenticated which provide the SP with the necessary information.

In order to properly understand how the SAML2 authentication takes place, I have done something I rarely do: drawn an honest-to-gosh diagram. SAML2 Authentication Request sequence diagram Although it may suspiciously resemble a UML sequence diagram, I have no idea if it is valid UML or not. The UML sequence diagram courtesy of UMLet, the best UML tool I have found.[1]
  1. The web browser sends a request to the application, which is intercepted by the SAML2 filter. This request is stored for future use by the filter, while
  2. The filter responds with a redirect status code, redirecting the browser to the SAML2 Identity Provider (IdP) and including a SAML2 Authentication Request, cryptographically signed, as a query parameter.
  3. The user, browser, and IdP collaborate (noisily sometimes) to authenticate the user.
  4. The IdP sends a final reply to the browser with a 200 status code; the page contains a JavaScript handler causing the browser to immediately POST the Authentication Reply as form contents to the URL of the ACS identified in the authentication request.
  5. The ACS decodes the Authentication Reply and notes that the user has been authenticated. It replies to the browser with a redirect back to the application's original URL.
  6. The filter intercepts the redirection, sees that the user has been authenticated, reactivates the original request, and forwards that request to the application proper.
  7. The application replies to the request, and
  8. Continues to respond to future user requests until the user logs out or the authentication expires.
The original authentication request is sent using SAML2's HTTP-Redirect binding, while the authentication reply is sent using SAML2's HTTP-POST binding. Other bindings, including an HTTP-Artifact binding, SOAP binding, and others, abound.

In practice, what that means is that the request made in step 2 takes the form of an HTTP redirect, with the Location being the URL of the IdP along with query parameters containing the authentication request, cryptographic signatures, and so on. The response in step 4 takes the form of a standard HTML page containing a form; hidden arguments in this form contain the authentication response along with its cryptographic signatures. This page includes a JavaScript onload handler that causes the form to be immetidately posted to the ACS' URL.

Upsides


Now, the sane way (a network protocol guy would think) to design a HTTP authentication method would be to use HTTP's somewhat-modular authentication support. However, doing that would require modifying web browsers to support the new scheme if it were not one of the ones already handled. And the existing schemes are pretty bare. The SAML approach requires only normal technology, assuming you accept a JavaScript onload handler that automatically POSTs a form containing data to be passed between the IdP and the ACS in step 4.

The SAML scheme allows Single-Sign-On by maintaining session information between the browser and the IdP; if the user has already authenticated or is capable of authenticating automatically via SPNEGO, for example then step 3 reduces down to the request to the IdP identifying the browser. In fact, because we are capable of sharing information between applications, any application using the SAML2 endpoint filter will use the same authentication information, removing the need to go through any kind of authentication step when moving between our supported applications.

While it is not currently used in our case, the SAML approach also supports privacy since the user only exchanges identifying information with the IdP and the SP and application trust the judgment of the IdP. The authentication response contains a user identifier, an effectively random token that is associated with the user either for this session or permanently, for multiple sessions. Optionally, the response also contains assertions about the user, including in our case the user's actual identifying information, such as a user id. If someone desired an application that should not know anything about a user other than that they were authentic, the SAML response could be configured to only contain the identifier, not the assertions.

Downsides


SAML is a three-humped camel with a tail like a '57 Chevy. [Ed: Really, the tail looks like a '57 Chevy.]

Seriously, the Security Assertion Markup Language is developed by OASIS. At its core is an XML specification for sharing security related assertions. SAML is, however, much bigger than its core. It includes what they call "protocols", defining what SAML-formatted messages should be sent under specific circumstances such as authentication request, logouts, assertion queries, etc. The SAML family also includes what they call "bindings", which provide the details about how protocol messages should be presented in standard message formats such as HTTP Redirect (used by the authentication request above) and HTTP POST (used by the authentication reply above), SOAP, etc. Also, there are "profiles"; "metadata" to be exchanged; conformance, security, and privacy considerations; and a three-legged water buffalo named Elmont.

For an example that puts the whole scheme in perspective, consider the Authentication Request (AuthnRequest, not to be confused with an Authorization Request, or AuthrRequest) made in step 2 above. The metadata I have suggests that that should be done using the HTTP Redirect binding, which means the request is made by returning a HTTP redirect response code with the arguments passed to the IdP in the Location header as query parameters. The AuthnRequest is an XML document identifying the requester, specifying the desired level of assurance, and so forth.

Since this AuthnRequest has to fit into a URL query parameter, it is compressed, Base64 encoded, and then ultimately URL encoded for transmission. (And that is ever so much better than simply specifying the arguments as query parameters.) However, the request should be cryptographically signed, which in this case means cryptographically hashing that part of the query string. The response, on the other hand, is sent using the HTTP POST binding, which uses more Base64 and XML Encryption standards for signing and encryption as well as the JavaScript onload handler trick.

Document formatting standards bodies may not provide the best network protocols, that is all I want to say.

There are some other irritations. To save some time and my sanity, I use the OpenAM client Java SDK jars to process the SAML messages since they seem to provide the best API. However, both the fmclientsdk.jar and openssoclientsdk.jar are required, even though they provide overlapping sets of packages and classes. Further, I remain dubious that the teething pains are over and the performance of this SSO mechanism leaves something to be desired, especially since the default authentication expiration seems to be about five minutes. Finally, I am not at all sure how this will play with asynchronous messaging and RPC mechanisms such as BlazeDS and browser-side JavaScript applications.

Postscript


By the way, you may be wondering why I carefully stored the original request in step 1 and restarted it in step 6. Instead, could I not just direct the user to a nice "Welcome" page? Consider what happens after a user has logged in and begun using the application: at some point, the user's identity information for the application will expire and the next request the user makes will appear to be unauthenticated. This expiration could happen after thirty minutes, or it could happen after five. (Hey, it's cheaper than having security escort ex-employees out of the building, right?) That request could be anything; an AMF/BlazeDS request, posting a form that will end-of-life a well-traveled, expensive piece of equipment, even a request for a safe welcome page, or whatever. Now, it is likely that storing the request will leave the application in some bizarro state anyway, but it seems like the best chance of handling the weirdness gracefully.

[Added 7/24/2010.]

[1] Which may not be saying much.
active directory applied formal logic ashurbanipal authentication books c c++ comics conference continuations coq data structure digital humanities Dijkstra eclipse virgo electronics emacs goodreads haskell http java job Knuth ldap link linux lisp math naming nimrod notation OpenAM osgi parsing pony programming language protocols python quote quotes R random REST ruby rust SAML scala scheme shell software development system administration theory tip toy problems unix vmware yeti
Member of The Internet Defense League
Site proudly generated by Hakyll.