Using transport layer authentication

This topic was ALMOST what I was looking for but it fell just short :slight_smile:

The example describes how to authN the client using the HTTP headers in the WS handshake but it doesn’t provide a means to pass these up to the application that gets the ReactiveSocket instance and begins interacting with the remote entity.

As a result, even though the application can be sure the remote party was authenticated they don’t know WHO the remote party is.

I logged an issue against the rsocket-js implementation along similar lines:

So, although this is not a “protocol” issue per se, it would be useful to hear from the architects if the transport abstraction intentionally excludes authN or if this is just how the implementations work today.

Ideally I’d like, when running an RSocket Server, to have a hook that I can use to:

  • intercept the incoming transport layer connection (WS or TCP)
  • AuthN the client (could use TLS when transport is TCP?), and
  • reject the connection if AuthN fails, OR,
  • attach some data to the ReactiveSocket that identifies the client so this is available to application which might perform AuthZ or have some business logic tied to the identity of the client.

In Java, this might be something like java.security.Principal which I think is already well integrated into how Spring does AuthN and AuthZ for WebSockets.

@robertroeser and @greg (or other rsocket protocol designers and implementors) I was hoping to get your take on this question.

I’d be happy to throw an PR together for the Javascript implementation but I’d be out of my depth on the others.

In my search for answers I noted that someone has asked a related question before:

I think it’s great that rsocket is unopinionated on auth but it would be very helpful if:

a) the docs were explicit on this and provided some guidance on how auth COULD be done (best practices result in good conventions which result in shared libraries that save people time and prevent DIY security which is almost always bad)

b) the implementations made it easier (possible?) to use authenticated transports

For some context on our use case. we’d like to accept incoming WebSocket connections from clients. We will authenticate them using a token in the headers of the HTTP Upgrade request that establishes the connection. We then use rsocket over WS for communication between the client and server.

We’d do something like this:

const server = new RSocketServer({
  getRequestHandler: (socket, payload, client) => {
    // New parameter: client 
    // Same object returned by the authenticationCallback
    //  provided to the RSocketWebSocketServer constructor (see below)
    return createResponder(socket, client)
  },

  transport: new RSocketWebSocketServer (
    { port: 443 },
    BufferEncoders,
    (socket, request) => {
      // New constructor argument: authenticationCallback
      // Matches signature of ws 'connection' event
      //  and invoked before creating the ReactiveStream
      // If the callback returns a falsy value the connection is rejected
      return authenticate(request)
    }
  ),
})

Hi,

If I understand what you’re asking - authentication could be done in the setup payload. The setup payload can take data and metadata which you could use to authenticate the connection. You can reject the connection in the acceptor by returning an error that the request isn’t authenticated.

Thanks,
Robert

No, authentication is done in the setup of the transport.

e.g. If the transport is WebSockets then authentication is done by sending a bearer token in the HTTP Upgrade request. The WS connection is only accepted if the token is valid and the client is authenticated. The authenticated WS connection is then used as the transport for rsocket.

e.g. If the transport is TCP, the client is authenticated using a client certificate and the secure AND authenticated socket is then used as the transport for rsocket.

I don’t believe the correct way to pass identity of the authenticated client up to the rsocket handler is in the setup payload, it is a property of the transport so putting it in the payload forces the handler to capture it and pass it around with the connection.

I think my example above demonstrates what I mean but can try to make it clearer if not.

Ok - so you want to authenticate, capture some information once connection is authed, and then setup RSocket with this information?

I need to think about this a bit - the SetupPayload was created for sending data like this.
Maybe we could add connection metadata that could attached to a connection.

I’ll do a POC with the JS implementation. It could be that using the setup payload is the right way to do it.

As far as I can tell that payload is always empty in the Js implementation