RFC 8887

A JSON Meta Application Protocol (JMAP) Subprotocol for WebSocket

πŸ“… published August 2020, from ietf

RFC 8887: A JMAP Subprotocol for WebSocket πŸ’–

Part 1 of 3 ✨

Okay bestie, this one's written by K. Murchison from Fastmail πŸ’Œ. It's an Internet Standards Track doc, published August 2020, with ISSN 2070-1721. Its header doesn't list any obsoletes or updates, so it isn't replacing or patching anything else. πŸ¦‹


Abstract 🌸

Okay so this doc defines a binding for the JSON Meta Application Protocol (JMAP) over a WebSocket transport layer. The WebSocket binding gives you higher performance than the current HTTP binding for JMAP. Main character energy πŸ‘‘

Status of This Memo πŸ“œ

This is an Internet Standards Track document, so it's the real deal πŸ”₯. It's a product of the Internet Engineering Task Force (IETF), it represents the consensus of the IETF community, it got public review, and the Internet Engineering Steering Group (IESG) approved it for publication. If you want more on Internet Standards, check Section 2 of RFC 7841.

For the current status, any errata, and how to give feedback, go to https://www.rfc-editor.org/info/rfc8887.

Copyright (c) 2020 IETF Trust and the persons identified as the document authors. All rights reserved.

This doc is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as in effect on the date of publication. Please read these carefully, because they spell out your rights and restrictions. Code Components pulled out of this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions, and they come without warranty as described in the Simplified BSD License 🫢.

Table of Contents 🧚

1.  Introduction
2.  Conventions Used in This Document
3.  Discovering Support for JMAP over WebSocket
4.  JMAP Subprotocol
  4.1.  Authentication
  4.2.  Handshake
  4.3.  WebSocket Messages
    4.3.1.  Handling Invalid Data
    4.3.2.  JMAP Requests
    4.3.3.  JMAP Responses
    4.3.4.  JMAP Request-Level Errors
    4.3.5.  JMAP Push Notifications
  4.4.  Examples
5.  Security Considerations
  5.1.  Connection Confidentiality and Integrity
  5.2.  Non-browser Clients
6.  IANA Considerations
  6.1.  Registration of the WebSocket JMAP Subprotocol
7.  References
  7.1.  Normative References
  7.2.  Informative References
Acknowledgments
Author's Address

1. Introduction ✨

Okay so JMAP [RFC8620] over HTTP [RFC7235] requires that every JMAP API request gets authenticated. Depending on the type of authentication the JMAP client uses and how the JMAP server is configured, authentication could be expensive in both time and resources. In those situations, reauthenticating for every single JMAP API request can hurt performance 😭.

The WebSocket [RFC6455] binding for JMAP fixes this performance hit. It authenticates just the WebSocket handshake request, and those credentials stay in effect for the whole life of the WebSocket connection. This binding supports JMAP API requests and responses, with optional support for push notifications πŸ””.

Also, the WebSocket binding for JMAP can optionally compress [RFC7692] both JMAP API requests and responses. Compressing HTTP responses is everywhere, but compressing HTTP requests has very low deployment, if any at all, so it isn't a realistic option for JMAP API requests over HTTP πŸ’€.

2. Conventions Used in This Document 🌈

Okay, so the BCP 14 key words are the ones that really matter here. The words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document get read the way BCP 14 [RFC2119] [RFC8174] describes, but only when, and only when, they're written in all capitals just like that πŸ™ˆ.

This doc uses the terminology from the core JMAP spec [RFC8620] πŸ“.

3. Discovering Support for JMAP over WebSocket πŸ”

The JMAP capabilities object comes back as part of the standard JMAP Session object (see Section 2 of [RFC8620]). Servers that support this spec MUST add a property named urn:ietf:params:jmap:websocket to the capabilities object. The value of that property is an object that MUST contain the following info about server capabilities:

  • url: "String" The wss-URI (see Section 3 of [RFC6455]) to use for starting a JMAP-over-WebSocket handshake. Colloquially, this is the "WebSocket URL endpoint" 🌸.
  • supportsPush: "Boolean" This is true if the server supports push notifications over the WebSocket, as described in Section 4.3.5 πŸ””.

Example:

"urn:ietf:params:jmap:websocket": {
  "url": "wss://server.example.com/jmap/ws/",
  "supportsPush": true
}

4. JMAP Subprotocol πŸ’…

So a WebSocket subprotocol is an application-level protocol that sits on top of a WebSocket connection. This doc specifies the WebSocket JMAP subprotocol, which carries JMAP API requests, responses, and optional push notifications through a WebSocket connection. Binary data is handled per Section 6 of [RFC8620] (over a separate HTTP connection or stream), or per a future extension to JMAP or to this spec πŸ¦‹.

4.1. Authentication πŸ”

A JMAP WebSocket connection gets authenticated when the user's credentials are presented in the HTTP request [RFC7235] that starts the WebSocket handshake. For recommendations on picking HTTP authentication schemes, see Section 8.2 of [RFC8620] 🧁.

4.2. Handshake 🀝

The JMAP WebSocket client and the JMAP WebSocket server negotiate the WebSocket JMAP subprotocol during the WebSocket handshake. They can do that either with an HTTP/1.1 Upgrade request (see Section 4 of [RFC6455]) or an HTTP/2 Extended CONNECT request (see Section 5 of [RFC8441]). The WebSocket JMAP subprotocol is also intended to run over future bindings of HTTP (for example, HTTP/3), as long as there's a defined mechanism for doing a WebSocket handshake over that binding ✨.

No matter which method is used for the handshake, the client MUST first do a TLS handshake on a JMAP WebSocket URL endpoint (Section 3) that has the "wss://" scheme (WebSocket over TLS). It has to follow the requirements for running that particular HTTP binding over TLS (see [RFC2818] and Section 4.1 of [RFC6455] for HTTP/1.1, and Section 9.2 of [RFC7540] for HTTP/2). If the TLS handshake fails, the client MUST close the connection. Otherwise, the client MUST make an authenticated HTTP request [RFC7235] on the encrypted connection, and it MUST include the value "jmap" in the list of protocols for the Sec-WebSocket-Protocol header field πŸ”₯.

The reply from the server MUST also contain a matching Sec-WebSocket-Protocol header field with a value of "jmap". Without that, a JMAP subprotocol connection doesn't get established πŸ™…β€β™€οΈ.

Once the handshake has successfully completed, the WebSocket connection is established and can carry JMAP API requests, responses, and optional push notifications. Other message types MUST NOT be transmitted over this connection 🚫.

The credentials used to authenticate the HTTP request that starts the handshake stay in effect for the whole WebSocket connection. If the user's authentication credentials expire, the server can either treat later requests as unauthenticated, or close the WebSocket connection. In the second case, the server MAY send a Close frame with a status code of 1008 (Policy Violation), as defined in Section 7.4.1 of [RFC6455] πŸ₯Ί.

4.3.5.1. Notification Format

Okay so every push notification is a standard StateChange object (see Section 7.1 of [RFC8620]) πŸ’Œ

Over a WebSocket, the spec adds one extra argument to the StateChange object πŸ”₯

  • pushState: "String" (optional)

A (preferably short) string that encodes the entire server state visible to the user, not just the objects returned in this call ✨

The whole point of pushState is to let a client instantly catch up on anything that changed while it was disconnected (see Section 4.3.5.2) πŸ¦‹ If the server doesn't support pushState tokens, the client has to send a bunch of /changes requests (see Section 5.2 of [RFC8620]) after reconnecting to get its state back in sync with the server 😭

4.3.5.2. Enabling Notifications

A client turns on push notifications for the current connection by sending a WebSocketPushEnable object to the server πŸŽ€ A WebSocketPushEnable object has the following properties:

  • @type: "String"

This MUST be the string WebSocketPushEnable πŸ’–

  • dataTypes: "String[]|null"

A list of data type names (for example, "Mailbox" or "Email") that the client is interested in πŸ“¬ A StateChange notification is only sent if the data for one of these types changes. Other types are left out of the TypeState object. If it's null, changes get pushed for all supported data types 🌈

  • pushState: "String" (optional)

The last pushState token that the client received from the server 🧚 Once the server gets a pushState token, it SHOULD immediately send all changes since that state token πŸ’…

4.3.5.3. Disabling Notifications

A client turns off push notifications for the current connection by sending a WebSocketPushDisable object to the server πŸ™ˆ A WebSocketPushDisable object has the following property:

  • @type: "String"

This MUST be the string WebSocketPushDisable πŸ“


4.4. Examples ✨

These examples show WebSocket JMAP opening handshakes, a JMAP Core/echo request and response, and then a closing handshake πŸŽ€ They assume the JMAP WebSocket URL endpoint has been advertised in the JMAP Session object with a path of "/jmap/ws/" and that TLS negotiation has already succeeded πŸ” Heads up: folding of header fields is only for editorial purposes πŸ’œ

Here's a WebSocket JMAP connection over HTTP/1.1 with push notifications for mail [RFC8621] turned on. This one assumes the client already cached pushState "aaa" from a previous connection πŸ“§

[[ From Client ]]                [[ From Server ]]

GET /jmap/ws/ HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Authorization: Basic Zm9vOmJhcg==
Sec-WebSocket-Key:
  dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: jmap
Sec-WebSocket-Version: 13
Origin: https://www.example.com

                                 HTTP/1.1 101 Switching Protocols
                                 Upgrade: websocket
                                 Connection: Upgrade
                                 Sec-WebSocket-Accept:
                                   s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
                                 Sec-WebSocket-Protocol: jmap

[WebSocket connection established]

WS_DATA
{
  "@type": "WebSocketPushEnable",
  "dataTypes": [ "Mailbox", "Email" ],
  "pushState": "aaa"
}

                                 WS_DATA
                                 {
                                   "@type": "StateChange",
                                   "changed": {
                                     "a456": {
                                       "Mailbox": "d35ecb040aab"
                                     }
                                   },
                                   "pushState": "bbb"
                                 }

WS_DATA
{
  "@type": "Request",
  "id": "R1",
  "using": [ "urn:ietf:params:jmap:core" ],
  "methodCalls": [
    [
      "Core/echo", {
        "hello": true,
        "high": 5
      },
      "b3ff"
    ]
  ]
}

                                 WS_DATA
                                 {
                                   "@type": "Response",
                                   "requestId": "R1",
                                   "methodResponses": [
                                     [
                                       "Core/echo", {
                                         "hello": true,
                                         "high": 5
                                       },
                                       "b3ff"
                                     ]
                                   ]
                                 }

WS_DATA
The quick brown fox jumps
 over the lazy dog.

                                 WS_DATA
                                 {
                                   "@type": "RequestError",
                                   "requestId": null,
                                   "type":
                             "urn:ietf:params:jmap:error:notJSON",
                                   "status": 400,
                                   "detail":
                             "The request did not parse as I-JSON."
                                 }

[A new email is received]

                                 WS_DATA
                                 {
                                   "@type": "StateChange",
                                   "changed": {
                                     "a123": {
                                       "Email": "0af7a512ce70"
                                     }
                                   }
                                   "pushState": "ccc"
                                 }

WS_CLOSE

                                 WS_CLOSE

[WebSocket connection closed]

Next up is a WebSocket JMAP connection on an HTTP/2 stream that also negotiates compression [RFC7692] 🌸

[[ From Client ]]                [[ From Server ]]

                                 SETTINGS
                                 SETTINGS_ENABLE_CONNECT_PROTOCOL = 1

HEADERS + END_HEADERS
:method = CONNECT
:protocol = websocket
:scheme = https
:path = /jmap/ws/
:authority = server.example.com
origin: https://example.com
authorization = Basic Zm9vOmJhcg==
sec-websocket-protocol = jmap
sec-websocket-version = 13
sec-websocket-extensions =
  permessage-deflate
origin = https://www.example.com

                                 HEADERS + END_HEADERS
                                 :status = 200
                                 sec-websocket-protocol = jmap
                                 sec-websocket-extensions =
                                   permessage-deflate

[WebSocket connection established]

DATA
WS_DATA
[compressed text]

                                 DATA
                                 WS_DATA
                                 [compressed text]

...

DATA + END_STREAM
WS_CLOSE

                                 DATA + END_STREAM
                                 WS_CLOSE

[WebSocket connection closed]
[HTTP/2 stream closed]

5. Security Considerations πŸ”’

Security matters here, bestie 😀 The security considerations for both WebSocket (see Section 10 of [RFC6455]) and JMAP (see Section 8 of [RFC8620]) apply to the WebSocket JMAP subprotocol πŸ›‘οΈ Specific security considerations are covered below πŸ‘€

5.1. Connection Confidentiality and Integrity

To keep data sent and received via JMAP over WebSocket confidential and intact, the WebSocket connection MUST use TLS 1.2 [RFC5246] or later, following the recommendations in BCP 195 [RFC7525] πŸ” Servers SHOULD support TLS 1.3 [RFC8446] or later πŸ’ͺ

5.2. Non-browser Clients

JMAP over WebSocket can be used by clients running inside a web browser and outside of one too 🌐 Because of that, the security considerations in Sections 10.2 and 10.1 of [RFC6455] apply to those respective environments 🧠


6. IANA Considerations 🌸

6.1. Registration of the WebSocket JMAP Subprotocol

Per this specification, IANA has registered the following in the "WebSocket Subprotocol Name Registry" within the "WebSocket Protocol Registries" πŸ“‹

Subprotocol Identifier:  jmap

Subprotocol Common Name:  WebSocket Transport for JMAP (JSON Meta
   Application Protocol)

Subprotocol Definition:  RFC 8887

7. References πŸ“š

7.1. Normative References

[RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
           Requirement Levels", BCP 14, RFC 2119,
           DOI 10.17487/RFC2119, March 1997,
           <https://www.rfc-editor.org/info/rfc2119>.

[RFC2818]  Rescorla, E., "HTTP Over TLS", RFC 2818,
           DOI 10.17487/RFC2818, May 2000,
           <https://www.rfc-editor.org/info/rfc2818>.

[RFC5246]  Dierks, T. and E. Rescorla, "The Transport Layer Security
           (TLS) Protocol Version 1.2", RFC 5246,
           DOI 10.17487/RFC5246, August 2008,
           <https://www.rfc-editor.org/info/rfc5246>.

[RFC6455]  Fette, I. and A. Melnikov, "The WebSocket Protocol",
           RFC 6455, DOI 10.17487/RFC6455, December 2011,
           <https://www.rfc-editor.org/info/rfc6455>.

[RFC7235]  Fielding, R., Ed. and J. Reschke, Ed., "Hypertext Transfer
           Protocol (HTTP/1.1): Authentication", RFC 7235,
           DOI 10.17487/RFC7235, June 2014,
           <https://www.rfc-editor.org/info/rfc7235>.

[RFC7525]  Sheffer, Y., Holz, R., and P. Saint-Andre,
           "Recommendations for Secure Use of Transport Layer
           Security (TLS) and Datagram Transport Layer Security
           (DTLS)", BCP 195, RFC 7525, DOI 10.17487/RFC7525, May
           2015, <https://www.rfc-editor.org/info/rfc7525>.

[RFC7540]  Belshe, M., Peon, R., and M. Thomson, Ed., "Hypertext
           Transfer Protocol Version 2 (HTTP/2)", RFC 7540,
           DOI 10.17487/RFC7540, May 2015,
           <https://www.rfc-editor.org/info/rfc7540>.

[RFC7692]  Yoshino, T., "Compression Extensions for WebSocket",
           RFC 7692, DOI 10.17487/RFC7692, December 2015,
           <https://www.rfc-editor.org/info/rfc7692>.

[RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
           2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
           May 2017, <https://www.rfc-editor.org/info/rfc8174>.

[RFC8441]  McManus, P., "Bootstrapping WebSockets with HTTP/2",
           RFC 8441, DOI 10.17487/RFC8441, September 2018,
           <https://www.rfc-editor.org/info/rfc8441>.

[RFC8446]  Rescorla, E., "The Transport Layer Security (TLS) Protocol
           Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018,
           <https://www.rfc-editor.org/info/rfc8446>.

[RFC8620]  Jenkins, N. and C. Newman, "The JSON Meta Application
           Protocol (JMAP)", RFC 8620, DOI 10.17487/RFC8620, July
           2019, <https://www.rfc-editor.org/info/rfc8620>.

7.2. Informative References

[RFC8621]  Jenkins, N. and C. Newman, "The JSON Meta Application
           Protocol (JMAP) for Mail", RFC 8621, DOI 10.17487/RFC8621,
           August 2019, <https://www.rfc-editor.org/info/rfc8621>.

Acknowledgments 🫢

Huge thanks to Neil Jenkins, Robert Mueller, and Chris Newman for contributing their ideas and support while this specification was being written πŸ’œπŸŒˆ

Author's Address ✨

okay so this is the very last bit, and it's where you find the author! πŸ’– Kenneth Murchison wrote this one, and he's over at Fastmail US LLC πŸ¦‹ Here are his details, bestie:

Kenneth Murchison
Fastmail US LLC
1429 Walnut Street, Suite 1201
Philadelphia, PA 19102
United States of America

Email: murch@fastmailteam.com
URI:   http://www.fastmail.com/

and that's the whole doc, we made it! πŸŒˆπŸ‘‘

πŸ“¦ where this page comes from