Rolling your own authentication for REST API's, part 2 - making a Token gesture
A significant factor in the wide adoption of the REST architecture is that it liberates Internet data from the browser: any application or device with the capacity to issue HTTP requests can consume a REST API, allowing its own users to interact seamlessly with the application providing that API. One obstacle, however, to implementation of any REST API provider is... that it liberates data from the browser. Why is this potentially problematic? Because an API provider cannot take for granted all of the security mechanisms that are now bundled with nearly every browser.
Arguably the biggest limitation is the possibility that the API consumer is unable to store session cookies – or, at the very least, will not respond to them in the same fashion. Domino, like many other web servers, relies upon session cookies to allow what is known as a "token" to be sent to the server in the headers of every request; this temporary token, issued to the user upon initial authentication, is encrypted data that allows Domino to securely identify the user without requiring their user name and password to be sent in plain text every time the user navigates to another page or submits information to the server. Because existing session cookies are automatically sent to the server with every request without requiring the design of the web page to specifically instruct the browser to do so, once a user is logged in, we know that their token will be sent every time and do not have to write any code to enforce this behavior. Without certainty that the API consumer supports cookies, however, we need another way to secure the application. The remainder of this article will describe one possible approach.
Before we proceed further, let's get something out of the way: several of the techniques we're about to explore take advantage of changes to the Domino platform planned for version 8.5.2, which at the time of this writing is scheduled to be released later this year. As IBM always reserves the right to remove pending features from the final release, some of what is discussed in this article may no longer be applicable once 8.5.2 is officially available. Assuming each of these features does make it into the product, however, their use in combination provides an easy and effective way of implementing reliable security for the type of API previously described.
Domino's built-in session authentication mechanism uses multiple layers of encryption to associate a temporary token with a single user. To be precise, an encryption key stored within an Internet Site configuration document allows the Domino server to decrypt the token being sent by the browser; once decrypted, the token contains both the name of the authenticated user and the expiration date-time of the token itself. Hence, Domino not only knows who the user is, but also whether the token is still valid. If the token has expired, was not valid to begin with, or identifies a user who has insufficient access to perform the operation associated with the request that included the token, Domino asks the user to attempt to authenticate again. Furthermore, the configuration document that stores the encryption key is itself encrypted; the server must be able to decrypt the document to even read the key that is then used to decrypt and create tokens. This mechanism is one of many reasons why Domino is more secure than most other web server platforms.
This article proposes a technique that does not involve true encryption; instead, it uses two different types of one-way hash encoding known, respectively, as MD2 and MD5. When encrypting data, a key is used to transform the original data into its encrypted representation. In order to decrypt the result, a key must be used to transform the encrypted data back into its original value. Hash encoding, on the other hand, is designed to transform plain text data into an encoded representation that cannot be decoded. Instead, any implementation of a given hashing algorithm is designed such that encoding the same value will always return the same encoded result.
For example, encoding the word "Domino" using one implementation of the MD2 hash algorithm that is built into Domino will always return a value of "AEB5929ED34F602FD98DB7917098AC5B". Looking at that value, you're probably thinking what most Lotus professionals might: "That looks like a UNID." You're absolutely right: the result of MD2-encoding any string value – regardless of the length of that string value – is an uppercase 32-character hexadecimal string, which is the same format used for the UNID of any note in a Domino database. Those of you who have worked with older versions of Notes and Domino might recognize that this format was also previously used for something else: storage of a user's HTTP password... although when this format was used for that purpose, the hexadecimal value was surrounded by parentheses. In fact, even in the current version of Notes and Domino, the Formula function @Password returns an MD2 encoding of the value passed to the function, surrounded by parentheses... this is the built-in implementation referred to moments ago.
This result carries with it a fascinating implication: performing an MD2 hash always results in a value that is valid for use as a UNID. As you may already know, the UNID is a read-write property for a database note. This provides us the luxury of setting "meaningful" identifiers for our documents, as long as our applications are designed such that we have an opportunity to set the UNID before the document is originally saved (changing a document's UNID after it has already been saved to disk causes a new copy of the document to be created). This technique offers significant performance benefits compared to traditional index-based data structures: we need not search for data within a larger collection; if we know what information uniquely identifies that document, we "know" its UNID simply by hashing that value, and can therefore go directly to it. Accessing a document via its UNID is always faster than retrieving it via a view or even searching for it within a full-text index. Basing an entire application's data structure on this technique also relieves the server of the burden of maintaining unnecessary indexes. As you'll soon see, however, this technique can also provide us fine-grained control over the security of our application's data.
At first glance, the latter use may seem like a contradiction. After all, if hash encoding always produces a predictable result, won't the result be insecure? In reality, because hash encoding is one-way, the only way to "decode" the result is to perform the same hash operation against a series of candidate values until a match is found. In other words, the less likely the original value is to be guessed by whoever or whatever is attempting to decode it, the longer it will take for a match to be found because more obvious values will be attempted first. While this is not an absolute guarantee against brute force intrusion attempts by unsavory types, the way in which we'll be using it later will be perfectly sufficient for securing most data.
Comments
This is going to sound pedantic, but @Password is not technically the MD2 standard algorithm. It's a variant on it that RSA implemented specifically for the Notes BSafe package. It includes an additional rolling checksum.
This wouldn't really be that important, EXCEPT that if you use @Password(String) to get a hash value in one place, and use the java.security.MessageDigest class to get a hash value for the same String in a different place, the results will not match. You have to pick your API vector and stick to it.
Again, I'm not being pedantic. People in the real world have been burned by not knowing the difference.
Posted by Nathan T. Freeman At 09:41:33 AM On 06/20/2010 | - Website - |
Posted by Tim Tripcony At 03:29:53 PM On 06/20/2010 | - Website - |
these are excellent articles and I am not through reading them properly yet but I have one question so far. Above you say
"might recognize that this format was also previously used for something else: storage of a user's HTTP password"
Is the HTTP Password not still stored in this format in 852? I am using a custom registration form that stores it that way and it works fine. Am I missing a trick ?
Sean
Posted by Sean Cull At 08:59:47 AM On 11/03/2010 | - Website - |
Posted by Tim Tripcony At 11:33:32 AM On 11/03/2010 | - Website - |