When you want to enforce authentication for your RESTful web services, the first thing you have to do is decide which authentication protocol you want to use. Internet protocols for authentication vary in their complexity and their perceived reliability. In Java land, most servlet containers support the protocols of Basic Authentication, Digest Authentication, and authentication using X.509 certificates. Let’s look into how each of these protocols works.
Basic Authentication is the simplest protocol available for performing authentication over HTTP. It involves sending a Base 64–encoded username and password within a request header to the server. The server checks to see if the username exists within its system and verifies the sent password. To understand the details of this protocol, let’s look at an example.
Say an unauthorized client tries to access one of our secure RESTful web services:
GET /customers/333 HTTP/1.1
Since the request does not contain any authentication information, the server would reply with an HTTP response of:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Basic realm="CustomerDB Realm"
The 401 response tells the client that it is not authorized to access the URI it tried to invoke on. The WWW-Authenticate header specifies which authentication protocol the client should use. In this case, Basic means Basic Authentication should be used. The realm attribute identifies a collection of secured resources on a website. The client can use the realm information to match against a username and password that is required for this specific URI.
To perform authentication, the client must send a request with the Authorization header set to a Base 64–encoded string of our username and a colon character, followed by the password. If our username is bburke and our password geheim, the Base 64–encoded string of bburke:geheim will be YmJ1cmtlOmdlaGVpbQ==. Put all this together, and our authenticated GET request would look like this:
GET /customers/333 HTTP/1.1 Authorization: Basic YmJ1cmtlOmdlaGVpbQ==
The client needs to send this Authorization header with each and every request it makes to the server.
The problem with this approach is that if this request is intercepted by a hostile entity on the network, the hacker can easily obtain the username and password and use it to invoke its own requests. Using an encrypted HTTP connection, HTTPS, solves this problem. With an encrypted connection, a rogue programmer on the network will be unable to decode the transmission and get at the Authorization header. Still, security-paranoid network administrators are very squeamish about sending passwords over the network, even if they are encrypted within SSL packets.
Although not used much anymore, Digest Authentication was invented so that clients would not have to send clear-text passwords over HTTP. It involves exchanging a set of secure MD5 hashes of the username, password, operation, URI, and optionally the hash of the message body itself. The protocol starts off with the client invoking an insecure request on the server:
GET /customers/333 HTTP/1.1
Since the request does not contain any authentication information, the server replies with an HTTP response of:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Digest realm="CustomerDB Realm", qop="auth,auth-int", nonce="12dcde223152321ab99cd", opaque="aa9321534253bcd00121"
Like before, a 401 error code is returned along with a WWW-Authenticate header. The nonce and opaqu attributes are special server-generated keys that will be used to build the subsequent authenticated request.
Like Basic Authentication, the client uses the Authorization header, but with digest-specific attributes. Here’s a request example:
GET /customers/333 HTTP/1.1 Authorization: Digest username="bburke", realm="CustomerDB Realm", nonce="12dcde223152321ab99cd", uri="/customers/333", qop="auth", nc=00000001, cnonce="43fea", response="11132fffdeab993421", opaque="aa9321534253bcd00121"
The nonce and opaque attributes are a copy of the values sent with the earlier WWW-Authenticate header. The uri attribute is the base URI you are invoking on. The nc attribute is a request counter that should be incremented by the client with each request. This prevents hostile clients from replaying a request. The cnonce attribute is a unique key generated by the client and can be anything the client wants. The response attribute is where all the meat is. It is a hash value generated with the following pseudocode:
H1 = md5("username:realm:password") H2 = md5("httpmethod:uri") response = md5(H1 + ":nonce:nc:cnonce:qop:" + H2)
If our username is bburke and our password geheim, the algorithm will resolve to this pseudocode:
H1 = md5("bburke:CustomerDB Realm:geheim") H2 = md5("GET:/customers/333") response = md5(H1 + ":12dcde223152321ab99cd:00000001:43fea:auth:" + H2)
When the server receives this request, it builds its own version of the response hash using its stored, secret values of the username and password. If the hashes match, the user and its credentials are valid.
One advantage of this approach is that the password is never used directly by the protocol. For example, the server doesn’t even need to store clear-text passwords. It can instead initialize its authorization store with prehashed values. Also, since request hashes are built with a nonce value, the server can expire these nonce values over time. This, combined with a request counter, can greatly reduce replay attacks.
The disadvantage to this approach is that unless you use HTTPS, you are still vulnerable to man-in-the-middle attacks, where the middleman can tell a client to use Basic Authentication to obtain a password.
When you buy things or trade stocks on the Internet, you use the HTTPS protocol to obtain a secure connection with the server. HTTPS isn’t only an encryption mechanism—it can also be used for authentication. When you first interact with a secure website, your browser receives a digitally signed certificate from the server that identifies it. Your browser verifies this certificate with a central authority like VeriSign. This is how you guarantee the identity of the server you are interacting with and make sure you’re not dealing with some man-in-the-middle security breach.
HTTPS can also perform two-way authentication. In addition to the client receiving a signed digital certificate representing the server, the server can receive a certificate that represents and identifies the client. When a client initially connects to a server, it exchanges its certificate and the server matches it against its internal store. Once this link is established, there is no further need for user authentication, since the certificate has already positively identified the user.
Client Certificate Authentication is perhaps the most secure way to perform authentication on the Web. The only disadvantage of this approach is the managing of the certificates themselves. The server must create a unique certificate for each client that wants to connect to the service. From the browser/human perspective, this can be a pain, as the user has to do some extra configuration to interact with the server.