Stream: subscriptions
Topic: How does a client authenticate with websockets?
Isaac Vetter (Jun 19 2019 at 14:35):
Browser-based websocket libraries don't generally support sending HTTP headers during the creation of a websocket connection. (See heroku's excellent explanation of websocket security, RFC 6455, and FHIRcast's security considerations page and proposed resolution to this problem).
Isaac Vetter (Jun 19 2019 at 14:35):
Since there's a single url on the FHIR server that all websocket clients connect to, the FHIR server only identifies clients through the bind/bound exchange of the Subscription resource id. This isn't authentication.
Isaac Vetter (Jun 19 2019 at 14:35):
The Subscriptions spec currently has this bullet, which I don't fully understand:
Client authenticates to server using a server-specified Web socket protocol (e.g. OAuth bearer token presentation).
Isaac Vetter (Jun 19 2019 at 14:35):
Can anyone explain to me what the "OAuth bearer token presentation using a server-specified Web socket protocol" means? I think this probably means that the client should pass it's bearer access_token in the Sec-WebSocket-Protocol
http header during the request.
Isaac Vetter (Jun 19 2019 at 14:35):
Should the server respond with the token in the same header in the response? What else should be in this header?
Isaac Vetter (Jun 19 2019 at 14:35):
It's a hack to authenticate via this header, but supporting browser websockets doesn't give many options.
Jenni Syed (Jun 19 2019 at 14:56):
@Amy Ballard Was able to do secure websockets with Grahame's server, I believe (at the last connectathon)
Jenni Syed (Jun 19 2019 at 14:57):
Which would also imply that @Grahame Grieve has implemented websocket security from a server perspective :)
John Moehrke (Jun 19 2019 at 14:58):
client auth could be done via mutual-authenticated-TLS... but, that is often not an optimal solution.
Jenni Syed (Jun 19 2019 at 14:58):
Or it's possible she tried and wasn't able to get connected...
Jenni Syed (Jun 19 2019 at 14:59):
I'm not entirely sure when mutual auth is the optimal solution ;)
John Moehrke (Jun 19 2019 at 14:59):
I'm not entirely sure when mutual auth is the optimal solution ;)
when no other solution is available.. :-)
John Moehrke (Jun 19 2019 at 15:00):
non-user situations in b-2-b...
John Moehrke (Jun 19 2019 at 15:02):
aka messaging... especially hl7 v2, v3, DICOM, etc... but also FHIR messaging... The main point is that the server doesn't need to make any decision about the client except that they are authorized to connect to this endpoint. That is to say there is no application level business rules (User Role, App, context, etc).
John Moehrke (Jun 19 2019 at 15:02):
which might be considerted the case in the subscription use of websockets as the notification channel.
Amy Ballard (Jun 19 2019 at 16:08):
@Jenni Syed I tired and was not able to connect securely. I tried passing the Authorization header with the bearer token, which didn't work. As @Isaac Vetter mentioned, passing HTTP headers is not supported by browsers anymore so not really a viable option anyway. Other than @Grahame Grieve's server, al other servers allowed me to connect by creating the connection with wss:// url with no additional authorization, so although the connection was secure anyone could connect.
Amy Ballard (Jun 19 2019 at 16:13):
It does seem that Grahame's server has something in place for authorization for secure websockets since is was returning a 401 when I attempted to connect
Grahame Grieve (Jun 19 2019 at 22:06):
I'll have to investigate. it worked without security?
Amy Ballard (Jun 20 2019 at 00:08):
@Grahame Grieve for your server I could connect with ws:// without security as expected. I didn't figure out how pass the auth token to get connected with wss:// . I was getting 401 as you'd expect for not getting the authorization correct. All the other servers at the connectathon had no authorization protecting their wss:// endpoints.
Gino Canessa (Jun 27 2019 at 16:25):
I've been researching/thinking about this since the call yesterday as well. From what I can see, the standard header-type authentications won't work because:
1) While headers are supported on ws/wss connections, implementation support for them is spotty at best.
2) Long-running connections (which these may be) have no way to re-authenticate (e.g., someone authorized today may not be tomorrow)
It appears that common practice is to setup some sort of challenge/response.
Based on yesterday's call, we were looking to define handshake and heartbeat messages (so far I have been leaning towards transaction bundles with extensions), and I thought that we could add an extension for authorization to these as well (e.g., a field to pass the equivalent headers).
Since we are looking at sending transaction bundles over the wire already, everyone should have tooling for this and I believe it meets all the requirements for auth.
Thoughts?
Gino Canessa (Jun 27 2019 at 18:07):
May make more sense visually:
WebsocketAuthIdea.png
Josh Mandel (Jun 28 2019 at 19:36):
@Isaac Vetter We had talked back in Montreal about WS clients in #FHIRcast using a protocol
header to pass along an OAuth token; did this get assessed and turned down?
Isaac Vetter (Jun 28 2019 at 21:12):
Hey @Josh Mandel ,
Isaac Vetter (Jun 28 2019 at 21:12):
It did get assessed and turned down, but just by me because I think it's ugly. Passing the access token in the protocol header (and subsequent problems/questions) is what I was trying to refer to in the seminal post of this topic.
Isaac Vetter (Jun 28 2019 at 21:12):
Note that websockets defines the Sec-WebSocket-Protocol
http header, which the client specifies during the initial request and which the server is supposed to echo back. Fundamentally, it's a hack to authenticate via this header, but supporting browser websockets doesn't give many options.
Isaac Vetter (Jun 28 2019 at 21:12):
I'd encourage you to read heroku's excellent explanation of websocket security, and FHIRcast's security considerations page and proposed resolution to this problem) (Also RFC 6455).
Isaac Vetter (Jun 28 2019 at 21:12):
I think that the current Argo proposal -- putting the access token in the body of the FHIR bundle is better than hacking the protocol, but still weird in that it puts an access token in a FHIR resource (how should logging work? why does my security layer now have to understand FHIR?)I like the single-use "ticket" url method that's drafted in FHRcast.
Gino Canessa (Jun 28 2019 at 21:19):
Hi @Isaac Vetter, I don't know that I'd call what I put above as a proposal yet, more just trying to understand what people have and what is desired.
That said, part of my concern in using a header on the connection is that the only way to re-auth is to kill the connection (e.g., the connection has been open 24 hours, and I want to revalidate the token). In the case of subscriptions, that can result in missed notifications.
On the other hand, you are right (and Josh brought it up also), that having it inside the message has odd implications as well.
Gino Canessa (Jun 28 2019 at 21:21):
It feels like there is no "perfect" solution, so I would like to fully understand the trade-offs before proposing something.
Isaac Vetter (Jun 28 2019 at 22:01):
Hey @Gino Canessa !
Isaac Vetter (Jun 28 2019 at 22:01):
I don't know that I'd call what I put above as a proposal yet,
Ah, I misunderstood then. I missed the argo call this week :embarrassed: on this and reading the notes/your post; it sounded like a done deal.
Isaac Vetter (Jun 28 2019 at 22:01):
In the case of subscriptions, that can result in missed notifications.
Isaac Vetter (Jun 28 2019 at 22:01):
Again, note the FHIRcast proposed resolution to this problem, is not a hack of the websockets header, but indeed does have the flaw of potential missed notifications during session re-connections.
Isaac Vetter (Jun 28 2019 at 22:01):
I hadn't understood that the goal was actual guaranteed message delivery. That's a hard problem for push notifications! Your handshake and heartbeat messages (and acknowledgements) are the beginnings of a full blown protocol, but even that doesn't completely prevent missed notifications.
Gino Canessa (Jun 28 2019 at 22:03):
Hey back @Isaac Vetter :grinning:
I know that I keep drifting into reliable delivery, but that's not the goal. We want to have a best-effort, but allow for the clients to detect/discover errors, and make sure there is a reasonable way to rebuild (other than wipe and start over).
Josh Mandel (Jun 28 2019 at 22:03):
The client opens the WebSocket connection, and sends along this “ticket” as part of an initial handshake.
This ticket might as well be an OAuth access token -- but how is it sent? What is "the initial handshake"? Are you saying in fhircast the handshake just "connect to a URL" (and visible to anyone logging HTTP GET traffic)?
Gino Canessa (Jun 28 2019 at 22:04):
I am just trying to flesh out the result of "Needs to be defined" and want to understand what people have been doing already, along with the pros and cons.
Isaac Vetter (Jun 28 2019 at 22:04):
HIRcast describes a "ticket"-based authentication system, in which the hub.topic url provided to the subscriber as part of the secured SMART app launch serves not only as a unique session identifier, but also as an "authorization ticket". This authorization ticket effectively acts as a bearer token. The Hub should therefore take care to generate opaque and unique hub.topic values.
https://fhircast.hl7.org/security-considerations/#experimental-websockets-support
Josh Mandel (Jun 28 2019 at 22:05):
If FHIRcast embeds the authenticate secret into the URL, what's the Subscription equivalent? Would we define... an operation like GET /Subscription/:id/$websocket-ticket
that returned a URL to connect to? Would we define a protocol to manage session renewal / expiration?
Josh Mandel (Jun 28 2019 at 22:05):
Indeed, I was reading that text from fhircast -- that's what my questions are about.
Isaac Vetter (Jun 28 2019 at 22:06):
what's the Subscription equivalent?
Yes. I imagined a 201 on the create with a wss:// url returned to the client.
Josh Mandel (Jun 28 2019 at 22:07):
Or maybe the closer analogy is to say that when a SMART app launches, it receives a generic "Websocket connection URL" that it can connect to as many times as it wants, and bind to different subscriptions in each connection.
Isaac Vetter (Jun 28 2019 at 22:07):
Would we define a protocol to manage session renewal / expiration?
No. Session/subscription end when the access token expires. Full stop. Create a new subscription.
Josh Mandel (Jun 28 2019 at 22:07):
Wait, subscriptions persist well across sessions though.
Josh Mandel (Jun 28 2019 at 22:07):
They can turn on and off, etc.
Isaac Vetter (Jun 28 2019 at 22:07):
not in FHIRcast
Josh Mandel (Jun 28 2019 at 22:07):
Understood.
Isaac Vetter (Jun 28 2019 at 22:07):
but, okay
Josh Mandel (Jun 28 2019 at 22:07):
I'm thinking about whether/how we could use that as a model in Subscriptions.
Josh Mandel (Jun 28 2019 at 22:08):
On the websocket side in fhircast, do I understand right that the "secret" is just the URL that a client does a GET
on to start the websocket connection?
Isaac Vetter (Jun 28 2019 at 22:08):
yup. So, a Subscription gets turned off when an access token expires.
Gino Canessa (Jun 28 2019 at 22:08):
In the case of Subscriptions, Josh, I believe the access token would just be a transient token value. It would only have meaning in the context of connecting to the wss:// url
Isaac Vetter (Jun 28 2019 at 22:08):
On the websocket side in fhircast, do I understand right that the "secret" is just the URL that a client does a GET on to start the websocket connection?
Yes
Gino Canessa (Jun 28 2019 at 22:09):
The client would then need to post another request, get the secret, and continue
Josh Mandel (Jun 28 2019 at 22:09):
Okay -- so generally in SMART we've avoided embedding secrets in URLs. It's not a hard prohibition, obviously... but we've avoided it.
Isaac Vetter (Jun 28 2019 at 22:09):
the alternatives are:
- embedding the secret in a FHIR resource
- embedding the secret in the name of a sub-protocol
Isaac Vetter (Jun 28 2019 at 22:10):
- embedding the secret in a newly created protocol layer wrapping all your websocket traffic
Isaac Vetter (Jun 28 2019 at 22:12):
- not supporting browser-based clients using native websocket libraries in FHIR Subscriptions
Josh Mandel (Jun 28 2019 at 22:12):
embedding the secret in a newly created protocol layer wrapping all your websocket traffic
I don't understand that one.
OTherwise, yes agree with this list.
Josh Mandel (Jun 28 2019 at 22:14):
i.e., put the secret in the url, headers, or body.
Those are the places.
Isaac Vetter (Jun 28 2019 at 22:15):
a newly created protocol layer wrapping your websocket traffic
This would be dividing the body into an auth header and the FHIR body -- like SOAP. The HTTP body is a SOAP header and a SOAP body.
Josh Mandel (Jun 28 2019 at 22:17):
OK -- makes sense and it's a sub-flavor of "body".
Gino Canessa (Jun 28 2019 at 22:26):
:+1:
John Silva (Jun 29 2019 at 13:01):
"missed notifications" -- can a FHIR Client request the 'current state' of the context when it reconnects? Is that sufficient? Are we're talking about the 'small window of time' when the Client's session is timing out and reestablishing a new session or are we talking about a FHIR client that 'went away' and then came back, e.g. the user exited an app and then restarted it on same desktop? (I assume the former)
Gino Canessa (Jul 01 2019 at 18:07):
What has been discussed is the handshake including the Subscription state information (but no way to pull back events). The reason for not forcing the events to be available is because that would be proscribing message-queuing, etc. to all servers wanting to implement. I can see that some servers may allow the behavior.
My original concern was around the time gap of re-authenticating (e.g., server times out token, client gets new one, client reconnects). Otherwise the client may need to rebuild every time a token expires, which does not feel like a robust process to me.
Talking through, there appear to be remedies for this, so it shouldn't be an issue.
Isaac Vetter (Jul 03 2019 at 01:16):
Hey @John Silva , also note that fhir subscriptions is describing a backend data integration and doesn't communicate the user session-type context as handled by FHIRcast. (I wanted to start this conversation to point out the gaps in the backend subscriptions websockets support that FHIRcast has already considered).
John Silva (Jul 03 2019 at 09:33):
@Marco Visser - OK, thanks
Abbie Watson (Sep 14 2019 at 22:15):
Just found this thread. We’ve been running FHIR resources over websockets for 3 or 4 years now via the Data Distribution Protocol (defined by MIT alumni). The general idea is protocol hoisting, which would be “embedding the secret in a newly created protocol layer”, and that newly created layer being DDP.
https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md
Michael Donnelly (Sep 14 2019 at 22:16):
Is that in prod?
Abbie Watson (Sep 14 2019 at 22:21):
Very much so. It works great, and we’ve done probably 40 or 50 prototypes and pilots with it. We have two instance (that I know of) published into the Apple App Store.
Michael Donnelly (Sep 14 2019 at 22:22):
Excellent.
Last updated: Apr 12 2022 at 19:14 UTC