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 thros 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 WWW-Authenticate 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="6e97a12828d940c7dc1ff24dad167d1f"
> 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.178.9