Skip navigation.

Authentication

CometD Authentication

Authentication is a complex task, and can be achieved in many ways, and most often each way is peculiar to a particular project.

Below you can find few ideas on how you can perform authentication.

Authentication via XMLHttpRequest

Authentication information could be passed by the CometD JavaScript client to the server via XMLHttpRequest.open(method, url, async, user, password), but this is currently not supported.
The main reason is that it will not work in the cross domain case, since XMLHttpRequest does not support cross domain authentication.

Authentication via HTTP request headers

Authentication information can be passed by the CometD JavaScript client to the server via HTTP request headers in each CometD request, and verified by the server (for example via a servlet filter).
It's possible to specify such request headers using the requestHeaders configuration parameter as specified here.

Authentication via Bayeux SecurityPolicy and Extension

This method is more complicated than previous ones, but gives more flexibility.
Authentication information can be passed by the CometD JavaScript client to the server during Bayeux handshake.
The CometD JavaScript client handshake() and init() methods take an extra object that's merged with the handshake message, for example:

cometd.configure({
    url: 'http://localhost:8080/myapp/cometd'
});

// Register a listener to be notified of authentication success or failure
cometd.addListener('/meta/handshake', function(message) 
{
    var auth = message.ext && message.ext.authentication;
    if (auth && auth.failed === true)
    {
        // Authentication failed, tell the user
        window.alert('Authentication failed!');
    }
});

var username = 'foo';
var password = 'bar';

// Send the authentication information
cometd.handshake({
    ext: {
        authentication: {
            user: username,
            credentials: password
        }
    }
});

The Bayeux specification suggests that authentication information is stored in the ext field of the handshake message (see here).

On the server, we need to configure the Bayeux object with a org.cometd.SecurityPolicy (to deny the handshake to clients that provide bad authentication information) and with an org.cometd.Extension to process the successful authentication.

It is possible to configure the Bayeux object using the same methods used to integrate Bayeux services (see also the Spring Integration).
For example, using a configuration servlet:

public class BayeuxInitializer extends GenericServlet
{
    @Override
    public void init() throws ServletException
    {
        Bayeux bayeux = (Bayeux)getServletContext().getAttribute(Bayeux.ATTRIBUTE);
        BayeuxAuthenticator authenticator = new BayeuxAuthenticator();
        bayeux.setSecurityPolicy(authenticator);
        bayeux.addExtension(authenticator);
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

Below you can find the code for the BayeuxAuthenticator class:

public class BayeuxAuthenticator extends AbstractBayeux.DefaultPolicy implements Extension, RemoveListener (1)
{
    @Override
    public boolean canHandshake(Message message)                                                           (2)
    {
        Map<String, Object> ext = message.getExt(false);
        if (ext == null)
            return false;

        // Be sure the client does not cheat us
        ext.remove("authenticationData");

        Map<String, Object> authentication = (Map<String, Object>)ext.get("authentication");
        if (authentication == null)
            return false;

        Object authenticationData = verify(authentication);                                                (3)
        if (authenticationData == null)
            return false;

        // Store the authentication result in the message for later processing
        ext.put("authenticationData", authenticationData);                                                 (4)

        return true;
    }

    public Message sendMeta(Client remote, Message responseMessage)                                        (5)
    {
        if (Bayeux.META_HANDSHAKE.equals(responseMessage.getChannel()))                                    (6)
        {
            Message requestMessage = responseMessage.getAssociated();                                      (7)

            Map<String, Object> requestExt = requestMessage.getExt(false);
            Object authenticationData = requestExt.get("authenticationData");
            if (authenticationData != null)
            {
                // Authentication successful

                // Link authentication data to the remote client                                           (8)

                // Be notified when the remote client disappears
                remote.addListener(this);                                                                  (9)
            }
            else
            {
                // Authentication failed
                
                // Add extra fields to the response                                                        (10)
                Map<String, Object> responseExt = responseMessage.getExt(true);
                Map<String, Object> authentication = new HashMap<String, Object>();
                responseExt.put("authentication", authentication);
                authentication.put("failed", true);

                // Tell the client to stop any further attempt to handshake                                (11)
                Map<String, Object> advice = new HashMap<String, Object>();
                advice.put(Bayeux.RECONNECT_FIELD, Bayeux.NONE_RESPONSE);
                responseMessage.put(Bayeux.ADVICE_FIELD, advice);
            }
        }
        return responseMessage;
    }

    public void removed(String clientId, boolean expired)                                                  (12)
    {
        // Unlink authentication data from the remote client                                               (13)
    }
    
    // Other methods omitted
}

Note that:

  • in (1) we make BayeuxAuthenticator be a SecurityPolicy, an Extension and a RemoveListener, since the code is really tied together
  • in (2) we override canHandshake() (a SecurityPolicy method), where we extract the authentication information from the message sent by the client
  • in (3) we verify the authentication information sent by the client, and obtain back server-side authentication data that we can later associate with the remote client
  • in (4) we store the server-side authentication data in the message to be processed later in (8)
  • in (5) we implement sendMeta() (an Extension method), which is called just after canHandshake() and just before sending the handshake response to the remote client
  • in (6) we perform our authentication logic only for handshake messages (i.e. messages that are sent on the handshake meta channel)
  • in (7) we retrieve the request message sent by the client from the response message that has been passed to sendMeta(), and we further retrieve the server-side authentication data set in (4)
  • in (8) we link the server-side authentication data to the remote client object
  • in (9) we register to be notified when the remote client disappears (which we will react to in (12))
  • in (10) we may want to add extra fields to the response message to detail why the authentication failed to the client
  • in (11) we set a Bayeux advice that tells the Bayeux client implementation to not retry to handshake automatically (since retrying automatically will resend the same credentials, and the authentication will fail again)
  • in (12) we implement removed() (a RemoveListener method), which is called when the remote client disappears, either because it disconnected or because it crashed
  • in (13) we unlink the server-side authentication data from the remote client object, the operation opposite to (8)

The reason for all this machinery is that at the moment of the invocation of canHandshake() the org.cometd.Client (server-side) object representing the remote client has not been created yet; it is only created if the handshake is allowed.

The most important steps are the number 8 and the number 13, where the server-side authentication data is linked/unlinked to/from the org.cometd.Client object.
This linking depends very much from project to project.
It may link a database primary key (of the row representing the user account) with the remote client id (obtained with remote.getId()), and/or viceversa, or it may link OAUTH tokens with the remote client id, etc.

Each Bayeux message always come with a client id, which can be thought as similar to the Http session id.
In the same way it is widespread practice to put the server-side authentication data in the HttpSession object (identified by the Http session id), in CometD web applications we can put server-side authentication data in a data structure where it can be later retrieved by Bayeux client id.
This data structure representing the "session" of a Bayeux client is not (yet) offered by the CometD API, and must be therefore coded by the CometD web application developer.

The Bayeux client ids are long, randomly generated numbers, exactly like Http session ids, and offer the same level security offered by a Http session id.
If an attacker manages to sniff a Bayeux client id, it can impersonate that Bayeux Client exactly in the same way it can sniff a Http session id and impersonate that Http session.
And, of course, the same solutions to this problem used to secure Http applications can be used to secure CometD web applications.