Skip navigation.

CometD 2 Authentication

CometD 2 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 well 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 BayeuxServer object with an implementation of org.cometd.bayeux.server.SecurityPolicy (to deny the handshake to clients that provide bad authentication information).

The BayeuxServer object need to be configured with the custom security policy, for example using a configuration servlet:

public class BayeuxInitializer extends GenericServlet
{
    @Override
    public void init() throws ServletException
    {
        BayeuxServer bayeux = (BayeuxServer)getServletContext().getAttribute(BayeuxServer.ATTRIBUTE);
        BayeuxAuthenticator authenticator = new BayeuxAuthenticator();
        bayeux.setSecurityPolicy(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 DefaultSecurityPolicy implements ServerSession.RemoveListener      (1)
{
    @Override
    public boolean canHandshake(BayeuxServer server, ServerSession session, ServerMessage message)          (2)
    {
        if (session.isLocalSession())                                                                       (3)
            return true;

        Map<String, Object> ext = message.getExt();
        if (ext == null)
            return false;

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

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

        // Authentication successful                                      

        // Link authentication data to the session                                                          (5)

        // Be notified when the session disappears
        session.addListener(this);                                                                          (6)

        return true;
    }

    public void removed(ServerSession session, boolean expired)                                             (7)
    {
        // Unlink authentication data from the remote client                                                (8)
    }
}

Note that:

  • in (1) we make BayeuxAuthenticator be a SecurityPolicy and a ServerSession.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 allow handshake for any server-side local session (such as those associated with services)
  • in (4) 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 (5) we link the server-side authentication data to the session
  • in (6) we register to be notified when the remote client disappears (which we will react to in (8))
  • in (7) we implement removed() (a RemoveListener method), which is called when the remote client disappears, either because it disconnected or because it crashed
  • in (8) we unlink the server-side authentication data from the remote client object, the operation opposite to (5)

The most important steps are the number 5 and the number 8, where the server-side authentication data is linked/unlinked to/from the org.cometd.bayeux.server.ServerSession 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 session id (obtained with session.getId()), and/or viceversa, or it may link OAUTH tokens with the remote session id, etc.

Each Bayeux message always come with a session 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 the ServerSession object.

The Bayeux session 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 session id, it can impersonate that Bayeux session 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.

Customizing the handshake response message

The handshake response message can be customized, for example adding an object to the ext field of the response, that specify further challenges data or the code/reason of the failure, and what action should be done by the client (for example, disconnecting or retrying the handshake).

This is an example of how the handshake response message can be customized in the SecurityPolicy:

public class MySecurityPolicy extends DefaultSecurityPolicy
{
    public boolean canHandshake(BayeuxServer server, ServerSession session, ServerMessage message)
    {
        if (!canAuthenticate(session, message))
        {
            // Retrieve the handshake response
            ServerMessage.Mutable handshakeReply = message.getAssociated();

            // Modify the advice, in this case tell to try again
            // If the advice is not modified it will default to disconnect the client
            Map advice = handshakeReply.getAdvice(true);
            advice.put(Message.RECONNECT_FIELD, Message.RECONNECT_HANDSHAKE_VALUE);
            
            // Modify the ext field with extra information on the authentication failure
            Map ext = handshakeReply.getExt(true);
            Map authentication = new HashMap();
            ext.put("com.acme.auth", authentication);
            authentication.put("failureReason", "invalid_credentials");
            return false;
        }
        return true;
    }
}

Alternatively, it is possible to customize the handshake response message by implementing a BayeuxServer.Extension:

public class HandshakeExtension implements BayeuxServer.Extension
{
    public boolean sendMeta(ServerSession to, ServerMessage.Mutable message)
    {
        if (Channel.META_HANDSHAKE.equals(message.getChannel()))
        {
            if (!message.isSuccessful())
            {
                Map advice = message.getAdvice(true);
                advice.put(Message.RECONNECT_FIELD, Message.RECONNECT_HANDSHAKE_VALUE);

                Map ext = message.getExt(true);
                Map authentication = new HashMap();
                ext.put("com.acme.auth", authentication);
                authentication.put("failureReason", "invalid_credentials");
            }
        }
    }

    // Other methods omitted
}