Using HTTP digest authentication
As support for HTTP, basic authentication is already built-in with Play. You can easily access
request.user and request.password in your controller as using digest authentication is
a little bit more complex. To be fair, the whole digest authentication is way more complex.
You can find the source code of this example in the chapter2/digest-auth directory.
Getting ready
Understanding HTTP authentication in general is quite useful, in order to grasp what is done in
this recipe. For every HTTP request the client wants to receive a resource by calling a certain
URL. The server checks this request and decides whether it should return either the content
or an error code and message telling the client to provide needed authentication. Now the
client can re-request the URL using the correct credentials and get its content or just do
nothing at all.
When using HTTP basic authentication, the client basically just sends some user/password
combination with its request and hopes it is correct. The main problem of this approach
is the possibility to easily strip the username and password from the request, as there are
no protection measures for basic authentication. Most people switch to an SSL-encrypted
connection in this case in order to mitigate this problem. While this is perfectly valid (and
often needed because of transferring sensitive data), another option is to use HTTP digest
authentication. Of course digest authentication does not mean that you cannot use SSL.
If all you are worrying about is your password and not the data you are transmitting, digest
authentication is just another option.
In basic authentication the user/password combination is sent in almost cleartext over the
wire. This means the password does not need to be stored as cleartext on the server side,
because it is a case of just comparing the hash value of the password by using MD5 or SHA1.
When using digest authentication, only a hash value is sent from client to server. This implies
that the client and the server need to store the password in cleartext in order to compute the
hash on both sides.
How to do it…
Create a user entity with these fields:
@Entity
public class User extends Model {
public String name;
public String password; // hashed password
public String apiPassword; // cleartext password
}
Create a controller which has a @Before annotation:
public class Application extends Controller {
@Before
static void checkDigestAuth() {
if (!DigestRequest.isAuthorized(request)) {
throw new UnauthorizedDigest("Super Secret Stuff");
}
}
public static void index() {
renderText("The date is " + new Date());
}
}
The controller throws an UnauthorizedDigest exception, which looks like this:
public class UnauthorizedDigest extends Result {
String realm;
public UnauthorizedDigest(String realm) {
this.realm = realm;
}
@Override
public void apply(Request request, Response response) {
response.status = Http.StatusCode.UNAUTHORIZED;
String auth = "Digest realm=" + realm + ", nonce=" +
Codec.UUID();
response.setHeader("WWW-Authenticate", auth);
}
}
The digest request handles the request and checks the authentication:
class DigestRequest {
private Map<String,String>params = new HashMap<String,String>();
private Request request;
public DigestRequest(Request request) {
this.request = request;
}
public booleanisValid() {
...
}
public booleanisAuthorized() {
User user = User.find("byName", params.get("username")).
first();
if (user == null) {
throw new UnauthorizedDigest(params.get("realm"));
}
String digest = createDigest(user.apiPassword);
return digest.equals(params.get("response"));
}
private String createDigest(String pass) {
...
}
public static booleanisAuthorized(Http.Request request) {
DigestRequest req = new DigestRequest(request);
return req.isValid() && req.isAuthorized();
}
}
How it works…
As you can see, all it takes is four classes. The user entity should be pretty clear, as it only
exposes three fields, one being a login and two being passwords. This is just to ensure that
you should never store a user’s master password in cleartext, but use additional passwords
if you implement some cleartext password dependant application.
The next step is a controller, which returns a HTTP 403 with the additional information
requiring HTTP digest authentication. The method annotated with the Before annotation
is always executed before any controller method as this is the perfect place to check for
authentication. The code checks whether the request is a valid authenticated request. If this
is not the case an exception is thrown. In Play, every Exception which extends from Result
actually can return the request or the response.
Taking a look at the UnauthorizedDigest class you will notice that it only changes
the HTTP return code and adds the appropriate WWW-Authenticate header. The WWWAuthenticate
header differs from the one used with basic authentication in two ways. First
it marks the authentication as “Digest”, but it also adds a so-called nonce, which should be
some random string. This string must be used by the client to create the hash and prevents
bruteforce attacks by never sending the same user/password/nonce hash combination.
The heart of this recipe is the DigestRequest class , which actually checks the request
for validity and also checks whether the user is allowed to authenticate with the credentials
provided or not. Before digging deeper, it is very useful to try the application using curl and
observing what the headers look like. Call curl with the following parameters:
curl --digest --user alex:test -v localhost:9000
The response looks like the following (unimportant output and headers have been stripped):
> GET / HTTP/1.1
> Host: localhost:9000
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< WWW-Authenticate: Digest realm=Super Secret Stuff, nonce=3ef81305-
745c-40b9-97d0-1c601fe262ab
< Content-Length: 0
<
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'HTTP://localhost:9000'
> GET / HTTP/1.1
> Authorization: Digest username="alex", realm="Super Secret Stuff",
nonce="3ef81305-745c-40b9-97d0-1c601fe262ab", uri="/", response="6e97a
12828d940c7dc1ff24dad167d1f"
> Host: localhost:9000
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=utf-8
< Content-Length: 20
<
This is top secret!
Curl actually issues two requests. The first returns a 403 "not authorized" error, but also the
nonce, which is used together with the username and password in the second request to
create the response field inside the WWW-Authenticate header. As the client also sends the
nonce and the username inside the header, the server can reconstruct the whole response on
the server side. This means it is actually stateless and the server does not need to store any
data between two requests.
Looking at the DigestRequest class, it is comprised of three core methods: isValid(),
isAuthorized() , and createDigest() . The isValid() method checks whether a
request contains all the needed data in order to be able to compute and compare the hash.
The isAuthorized() method does a database lookup of the user's cleartext password
and hands it over to the createDigest method , which computes the response hash and
returns true if the computed hash with the local password is the same as the hash sent in the
request. If they are not, the authentication has to fail.
The static DigestRequest.isAuthorized() method is a convenient method to keep the
code in the controller as short as possible.
There are two fundamental disadvantages in the preceding code snippet. First, it is
implementation dependent, because it directly relies on the user entity and the password field
of this entity. This is not generic and has to be adapted for each implementation. Secondly,
it only implements the absolute minimum subset of HTTP digest authentication. Digest
authentication is quite complex if you want to support it with all its variations and options.
There are many more options and authentication options, hashing algorithms, and optional
fields which have to be supported in order to be RFC-compliant. You should see this only
as a minimum starting point to get this going. Also this should not be thought of as secure,
because without an additional header called "qop", every client will switch to a less secure
mode. You can read more about that in RFC2069 and RFC2617.
There's more...
You can also verify this recipe in your browser by just pointing it to http://localhost:9000/.
An authentication window requiring you to enter username and password will popup.
Get more info about HTTP digest authentication
As this recipe has not even covered five percent of the specification, you should definitely read
the corresponding RFC at http://tools.ietf.org/html/rfc2617 as well as RFC2069
at http://tools.ietf.org/html/rfc2617.
See also
In order to be application-independent you could use annotations to mark the field of
the entity to be checked. The recipe Rendering JSON output will show you how to use an
annotation to mark a field not to be exported via JSON.






September 20, 2011
Java