Third party invites

This module adds in support for inviting new members to a room where their Matrix user ID is not known, instead addressing them by a third party identifier such as an email address. There are two flows here; one if a Matrix user ID is known for the third party identifier, and one if not. Either way, the client calls /invite with the details of the third party identifier.

The homeserver asks the identity server whether a Matrix user ID is known for that identifier:

When the invitee’s homeserver receives the notification of the binding, it should insert an m.room.member event into the room’s graph for that user, with content.membership = invite, as well as a content.third_party_invite property which contains proof that the invitee does indeed own that third party identifier. See the m.room.member schema for more information.

Events

m.room.third_party_invite


Acts as an m.room.member invite event, where there isn’t a target user_id to invite. This event contains a token and a public key whose private key must be used to sign the token. Any user who can present that signature may use this invitation to join the target room.

Event type: State event
State key The token, of which a signature must be produced in order to join the room.

Content

Name Type Description
display_name string Required: A user-readable string which represents the user who has been invited. This should not contain the user’s third party ID, as otherwise when the invite is accepted it would leak the association between the matrix ID and the third party ID.
key_validity_url string Required: A URL which can be fetched, with querystring public_key=public_key, to validate whether the key has been revoked. The URL must return a JSON object containing a boolean property named ‘valid’.
public_key string Required: A base64-encoded ed25519 key with which token must be signed (though a signature from any entry in public_keys is also sufficient). This exists for backwards compatibility.
public_keys [PublicKeys] Keys with which the token may be signed.
PublicKeys
Name Type Description
key_validity_url string An optional URL which can be fetched, with querystring public_key=public_key, to validate whether the key has been revoked. The URL must return a JSON object containing a boolean property named ‘valid’. If this URL is absent, the key must be considered valid indefinitely.
public_key string Required: A base-64 encoded ed25519 key with which token may be signed.

Examples

{
  "content": {
    "display_name": "Alice Margatroid",
    "key_validity_url": "https://magic.forest/verifykey",
    "public_key": "abc123",
    "public_keys": [
      {
        "key_validity_url": "https://magic.forest/verifykey",
        "public_key": "def456"
      }
    ]
  },
  "event_id": "$143273582443PhrSn:example.org",
  "origin_server_ts": 1432735824653,
  "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
  "sender": "@example:example.org",
  "state_key": "pc98",
  "type": "m.room.third_party_invite",
  "unsigned": {
    "age": 1234
  }
}

Client behaviour

A client asks a server to invite a user by their third party identifier.

POST /_matrix/client/r0/rooms/{roomId}/invite


Note that there are two forms of this API, which are documented separately. This version of the API does not require that the inviter know the Matrix identifier of the invitee, and instead relies on third party identifiers. The homeserver uses an identity server to perform the mapping from third party identifier to a Matrix identifier. The other is documented in the joining rooms.

This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.

Only users currently in a particular room can invite other users to join that room.

If the identity server did know the Matrix user identifier for the third party identifier, the homeserver will append a m.room.member event to the room.

If the identity server does not know a Matrix user identifier for the passed third party identifier, the homeserver will issue an invitation which can be accepted upon providing proof of ownership of the third party identifier. This is achieved by the identity server generating a token, which it gives to the inviting homeserver. The homeserver will add an m.room.third_party_invite event into the graph for the room, containing that token.

When the invitee binds the invited third party identifier to a Matrix user ID, the identity server will give the user a list of pending invitations, each containing:

  • The room ID to which they were invited

  • The token given to the homeserver

  • A signature of the token, signed with the identity server’s private key

  • The matrix user ID who invited them to the room

If a token is requested from the identity server, the homeserver will append a m.room.third_party_invite event to the room.

Rate-limited: Yes
Requires authentication: Yes

Request

Request parameters

path parameters
Name Type Description
roomId string Required: The room identifier (not alias) to which to invite the user.

Request body

Name Type Description
address string Required: The invitee’s third party identifier.
id_access_token string Required: An access token previously registered with the identity server. Servers can treat this as optional to distinguish between r0.5-compatible clients and this specification version.
id_server string Required: The hostname+port of the identity server which should be used for third party identifier lookups.
medium string Required: The kind of address being passed in the address field, for example email.

Request body example

{
  "address": "cheeky@monkey.com",
  "id_access_token": "abc123_OpaqueString",
  "id_server": "matrix.org",
  "medium": "email"
}

Responses

Status Description
200 The user has been invited to join the room.
403

You do not have permission to invite the user to the room. A meaningful errcode and description error text will be returned. Example reasons for rejections are:

  • The invitee has been banned from the room.
  • The invitee is already a member of the room.
  • The inviter is not currently in the room.
  • The inviter’s power level is insufficient to invite users to the room.
429 This request was rate-limited.

403 response

Name Type Description
errcode string Required: An error code.
error string A human-readable error message.
{
  "errcode": "M_FORBIDDEN",
  "error": "@cheeky_monkey:matrix.org is banned from the room"
}

429 response

Name Type Description
errcode string Required: The M_LIMIT_EXCEEDED error code
error string A human-readable error message.
retry_after_ms integer The amount of time in milliseconds the client should wait before trying the request again.
{
  "errcode": "M_LIMIT_EXCEEDED",
  "error": "Too many requests",
  "retry_after_ms": 2000
}

Server behaviour

Upon receipt of an /invite, the server is expected to look up the third party identifier with the provided identity server. If the lookup yields a result for a Matrix User ID then the normal invite process can be initiated. This process ends up looking like this:

+---------+                         +-------------+                                    +-----------------+
| Client  |                         | Homeserver  |                                    | IdentityServer  |
+---------+                         +-------------+                                    +-----------------+
    |                                     |                                                    |
    | POST /invite                        |                                                    |
    |------------------------------------>|                                                    |
    |                                     |                                                    |
    |                                     | GET /lookup                                        |
    |                                     |--------------------------------------------------->|
    |                                     |                                                    |
    |                                     |                                     User ID result |
    |                                     |<---------------------------------------------------|
    |                                     |                                                    |
    |                                     | Invite process for the discovered User ID          |
    |                                     |------------------------------------------          |
    |                                     |                                         |          |
    |                                     |<-----------------------------------------          |
    |                                     |                                                    |
    |        Complete the /invite request |                                                    |
    |<------------------------------------|                                                    |
    |                                     |                                                    |

However, if the lookup does not yield a bound User ID, the homeserver must store the invite on the identity server and emit a valid m.room.third_party_invite event to the room. This process ends up looking like this:

+---------+                         +-------------+                                               +-----------------+
| Client  |                         | Homeserver  |                                               | IdentityServer  |
+---------+                         +-------------+                                               +-----------------+
    |                                     |                                                               |
    | POST /invite                        |                                                               |
    |------------------------------------>|                                                               |
    |                                     |                                                               |
    |                                     | GET /lookup                                                   |
    |                                     |-------------------------------------------------------------->|
    |                                     |                                                               |
    |                                     |                                             "no users" result |
    |                                     |<--------------------------------------------------------------|
    |                                     |                                                               |
    |                                     | POST /store-invite                                            |
    |                                     |-------------------------------------------------------------->|
    |                                     |                                                               |
    |                                     |          Information needed for the m.room.third_party_invite |
    |                                     |<--------------------------------------------------------------|
    |                                     |                                                               |
    |                                     | Emit m.room.third_party_invite to the room                    |
    |                                     |-------------------------------------------                    |
    |                                     |                                          |                    |
    |                                     |<------------------------------------------                    |
    |                                     |                                                               |
    |        Complete the /invite request |                                                               |
    |<------------------------------------|                                                               |
    |                                     |                                                               |

All homeservers MUST verify the signature in the event’s content.third_party_invite.signed object.

The third party user will then need to verify their identity, which results in a call from the identity server to the homeserver that bound the third party identifier to a user. The homeserver then exchanges the m.room.third_party_invite event in the room for a complete m.room.member event for membership: invite for the user that has bound the third party identifier.

If a homeserver is joining a room for the first time because of an m.room.third_party_invite, the server which is already participating in the room (which is chosen as per the standard server-server specification) MUST validate that the public key used for signing is still valid, by checking key_validity_url in the above described way.

No other homeservers may reject the joining of the room on the basis of key_validity_url, this is so that all homeservers have a consistent view of the room. They may, however, indicate to their clients that a member’s membership is questionable.

For example, given H1, H2, and H3 as homeservers, UserA as a user of H1, and an identity server IS, the full sequence for a third party invite would look like the following. This diagram assumes H1 and H2 are residents of the room while H3 is attempting to join.

+-------+ +-----------------+         +-----+                                          +-----+           +-----+                      +-----+
| UserA | | ThirdPartyUser  |         | H1  |                                          | H2  |           | H3  |                      | IS  |
+-------+ +-----------------+         +-----+                                          +-----+           +-----+                      +-----+
    |              |                     |                                                |                 |                            |
    | POST /invite for ThirdPartyUser    |                                                |                 |                            |
    |----------------------------------->|                                                |                 |                            |
    |              |                     |                                                |                 |                            |
    |              |                     | GET /lookup                                    |                 |                            |
    |              |                     |---------------------------------------------------------------------------------------------->|
    |              |                     |                                                |                 |                            |
    |              |                     |                                                |                Lookup results (empty object) |
    |              |                     |<----------------------------------------------------------------------------------------------|
    |              |                     |                                                |                 |                            |
    |              |                     | POST /store-invite                             |                 |                            |
    |              |                     |---------------------------------------------------------------------------------------------->|
    |              |                     |                                                |                 |                            |
    |              |                     |                                                |      Token, keys, etc for third party invite |
    |              |                     |<----------------------------------------------------------------------------------------------|
    |              |                     |                                                |                 |                            |
    |              |                     | (Federation) Emit m.room.third_party_invite    |                 |                            |
    |              |                     |----------------------------------------------->|                 |                            |
    |              |                     |                                                |                 |                            |
    |           Complete /invite request |                                                |                 |                            |
    |<-----------------------------------|                                                |                 |                            |
    |              |                     |                                                |                 |                            |
    |              | Verify identity     |                                                |                 |                            |
    |              |-------------------------------------------------------------------------------------------------------------------->|
    |              |                     |                                                |                 |                            |
    |              |                     |                                                |                 |          POST /3pid/onbind |
    |              |                     |                                                |                 |<---------------------------|
    |              |                     |                                                |                 |                            |
    |              |                     |                         PUT /exchange_third_party_invite/:roomId |                            |
    |              |                     |<-----------------------------------------------------------------|                            |
    |              |                     |                                                |                 |                            |
    |              |                     | Verify the request                             |                 |                            |
    |              |                     |-------------------                             |                 |                            |
    |              |                     |                  |                             |                 |                            |
    |              |                     |<------------------                             |                 |                            |
    |              |                     |                                                |                 |                            |
    |              |                     | (Federation) Emit m.room.member for invite     |                 |                            |
    |              |                     |----------------------------------------------->|                 |                            |
    |              |                     |                                                |                 |                            |
    |              |                     |                                                |                 |                            |
    |              |                     | (Federation) Emit the m.room.member event sent to H2             |                            |
    |              |                     |----------------------------------------------------------------->|                            |
    |              |                     |                                                |                 |                            |
    |              |                     | Complete /exchange_third_party_invite/:roomId request            |                            |
    |              |                     |----------------------------------------------------------------->|                            |
    |              |                     |                                                |                 |                            |
    |              |                     |                                                |                 | Participate in the room    |
    |              |                     |                                                |                 |------------------------    |
    |              |                     |                                                |                 |                       |    |
    |              |                     |                                                |                 |<-----------------------    |
    |              |                     |                                                |                 |                            |

Note that when H1 sends the m.room.member event to H2 and H3 it does not have to block on either server’s receipt of the event. Likewise, H1 may complete the /exchange_third_party_invite/:roomId request at the same time as sending the m.room.member event to H2 and H3. Additionally, H3 may complete the /3pid/onbind request it got from IS at any time - the completion is not shown in the diagram.

H1 MUST verify the request from H3 to ensure the signed property is correct as well as the key_validity_url as still being valid. This is done by making a request to the identity server /isvalid endpoint, using the provided URL rather than constructing a new one. The query string and response for the provided URL must match the Identity Service Specification.

The reason that no other homeserver may reject the event based on checking key_validity_url is that we must ensure event acceptance is deterministic. If some other participating server doesn’t have a network path to the keyserver, or if the keyserver were to go offline, or revoke its keys, that other server would reject the event and cause the participating servers' graphs to diverge. This relies on participating servers trusting each other, but that trust is already implied by the server-server protocol. Also, the public key signature verification must still be performed, so the attack surface here is minimized.

Security considerations

There are a number of privacy and trust implications to this module.

It is important for user privacy that leaking the mapping between a matrix user ID and a third party identifier is hard. In particular, being able to look up all third party identifiers from a matrix user ID (and accordingly, being able to link each third party identifier) should be avoided wherever possible. To this end, the third party identifier is not put in any event, rather an opaque display name provided by the identity server is put into the events. Clients should not remember or display third party identifiers from invites, other than for the use of the inviter themself.

Homeservers are not required to trust any particular identity server(s). It is generally a client’s responsibility to decide which identity servers it trusts, not a homeserver’s. Accordingly, this API takes identity servers as input from end users, and doesn’t have any specific trusted set. It is possible some homeservers may want to supply defaults, or reject some identity servers for its users, but no homeserver is allowed to dictate which identity servers other homeservers' users trust.

There is some risk of denial of service attacks by flooding homeservers or identity servers with many requests, or much state to store. Defending against these is left to the implementer’s discretion.