Remote Authentication (Desktop)
In the context of remote authentication, the desktop client is the device that wants to be authenticated by the already logged-in mobile client. See the introduction for more information.
Remote Authentication Gateway
The desktop implementation of remote authentication uses WebSocket connections, operating similarly to the Gateway connection. However, it has a separate set of payloads and events, as well as a simplified packet structure.
Gateway Versions
Version | Status | Default |
---|---|---|
2 | Available | ✓ Client |
1 | Discontinued |
Gateway Payloads
In practice, opcodes are lower-cased with under_scores joining each word in the name. For instance, Nonce Proof would be nonce_proof
.
For readability, opcodes in the following documentation are typically left in Title Case.
Remote authentication Gateway event payloads are flat packets, with the op
field indicating the type of payload, and the rest of the fields being the payload data.
Example Gateway Payload
{"op": "hello","timeout_ms": 142637,"heartbeat_interval": 41250}
Gateway Commands
Name | Description |
---|---|
Init | Start a new remote auth session |
Heartbeat | Maintain an active WebSocket connection |
Nonce Proof | Submit a cryprographic proof of the handshake |
Gateway Events
Name | Description |
---|---|
Hello | Defines the heartbeat and timeout intervals |
Heartbeat ACK | Acknowledges a received client heartbeat |
Nonce Proof | Requests a cryptographic proof of the handshake |
Pending Remote Init | Acknowledges a successful handshake |
Pending Ticket | Acknowledges a successful mobile session creation |
Pending Login | Indicates that the mobile session was finished |
Cancel | Indicates that the mobile session was canceled |
Gateway Close Event Codes
Code | Description | Explanation |
---|---|---|
1000 | Normal closure | The remote auth session was finished or canceled successfully |
4000 | Unknown error | We're not sure what went wrong; try reconnecting? |
4001 | Handshake failure | The initial handshake failed; maybe reconnect and try again? |
4002 | Decode error | You sent an invalid payload; don't do that! |
4003 | Timeout | Your session timed out; reconnect and start a new one |
Connecting
For the remote authentication Gateway, the URL you can use to open a WebSocket connection is static.
When connecting to the URL, you must explicitly pass the API version as a query parameter. For example, wss://remote-auth-gateway.discord.gg/?v=2
is a URL a client may use to connect to the Gateway.
Endpoint
Query String Params
Field | Type | Description |
---|---|---|
v | integer | API Version to use |
Handshaking
Once you open a connection to the Gateway, you will receive an Opcode Hello payload, which contains the heartbeat interval and timeout duration. See the heartbeating section for more information.
At this point, you should generate a 2048-bit RSA-OAEP keypair that will be used in further communications. This will be verified by the Gateway using a nonce proof. An example in Python pseudocode would be:
Hello Structure
Field | Type | Description |
---|---|---|
heartbeat_interval | integer | The minimum interval (in milliseconds) the client should heartbeat at |
timeout_ms 1 | integer | The lifespan of the remote auth session (in milliseconds) before the connection is closed, typically a few minutes |
1 When the timeout duration passes, the Gateway will close with a 4003
close code.
Example Hello
{"op": "hello","timeout_ms": 142637,"heartbeat_interval": 41250}
Upon receiving the Opcode Hello payload, you should immediately send an Opcode Init containing the public key that all future communication will be encrypted with.
An example in Python pseudocode would be:
Init Structure
Field | Type | Description |
---|---|---|
encoded_public_key | string | The base64-encoded SPKI of the client's 2048-bit RSA-OAEP public key |
Example Init
{"op": "init","encoded_public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo2PGAKj4v6r6sPJtgJe2eIDCM8uEHKpYCSDmp+pun9vqiqPt4pDToS1vGtwTwc5hKKqtIo+I/5veBpGWSD/veuB0xVb/JbkPn847Q+mXAb6c9vRMJVkA7l9GaZdN49U5bnGJi009aNBoy9cAcP/19H6TLpHmZ9RojnqGqlCUdyAiqceTDTzPqov4ST3GJSyKPydL3ZVpPf5P/PGyNfISuESKA2CxGCoBvB4H6/FH7cwSFelyqhwwHPZcyxBjF/3iXx+k1PdS01y0NoTRun4p76bE9rWnecIWONPFvCkby8Xs/OqQ8QcAoLkfVj5L29Ut1+Kmwwfg3nzc4glZa6RuTwIDAQAB"}
If the Gateway accepts the public key, it will respond with an Opcode Nonce Proof payload, which contains a nonce that you must decrypt with your private key.
Nonce Proof Structure (Receive)
Field | Type | Description |
---|---|---|
encrypted_nonce | string | The base64-encoded nonce encrypted with the client's public key |
Example Nonce Proof (Receive)
{"op": "nonce_proof","encrypted_nonce": "GrOYi2wz9athue0mTNrdlnWGqJYkqu8tPe2uGr9V7CRwqVWDjgUI06gsPKszkORgB92P1P84V04fo7hG0tkQ/kDVNbVguACKfE4AIUUQXQFSkTVaGbZ2+FsItsoqOd+955EvkBK2oMz+kWlYILTQcISip9g5ZrY9SoKQvk7HDW9DliSteZivHzXQOc5RyecyeexOcV8oyC1zTk+uyVUTv3g7fcZ1Y81AK6u4+hvnEKGjOyEn+lbOotkNwcMC02xyVBX3IysSuNXf/f8/6gPMBNUHXEtlvhYx9AMCsPrPKkiilV7HpLN3oIvAfsZnyxbYcNiC6YS7z7VIPRaXEWW76w=="}
You should then send an Opcode Nonce Proof payload containing a base64URL-encoded representation of the decrypted nonce's SHA-256 digest.
An example in Python pseudocode would be:
Nonce Proof Structure (Send)
Field | Type | Description |
---|---|---|
nonce | string | The base64URL-encoded SHA-256 digest of the decrypted nonce |
Example Nonce Proof (Send)
{"op": "nonce_proof","nonce": "1xFdzsYTFMtZuUarGNKpBpMANyqNpRXpDFaxl8wFeQBUnVuY60d9j-or80f36xusAVTmUL90jwHrxMVPGZXJ8ahqDAiXByEOjkreJXZdPRbnvDkHHyqUP0QgHQBvrKq5Cba9-SDO8pFSc9T1YMWGut7n34xx6txX-QR7wDcZAoghq5EBl04WRlnt1DWfX2wMA7NuL1GFIZdw10IedBZ13E72BXnDasX97XMX_ldbxKwee6ABGf18zde2oHbTqw"}
If the Gateway accepts the nonce proof, it will respond with an Opcode Pending Remote Init payload, which indicates that the key exchange was successful and the remote auth session is ready.
At this point, the fingerprint can be used to create a remote auth session on the mobile client.
The fingerprint is typically communicated using a QR code with the following format: https://discord.com/ra/<fingerprint>
.
Pending Remote Init Structure
Field | Type | Description |
---|---|---|
fingerprint | string | The base64URL-encoded SHA-256 digest of the client's public key |
Example Pending Remote Init
{"op": "pending_remote_init","fingerprint": "UZ0-kOVzXDZTFVV5_QlpURSO2BQHrtkKWHNpIGoDI0k"}
Heartbeating
In order to maintain your WebSocket connection, you need to continuously send heartbeats at the interval determined in Opcode Hello:
This heartbeat interval is the minimum interval you should heartbeat at. You can heartbeat at a faster interval if you wish.
After receiving Opcode Hello, you should send Opcode Heartbeat every elapsed interval:
In return, you will be sent back an Opcode Heartbeat ACK. If a client does not receive a heartbeat ACK between its attempts at sending heartbeats, this may be due to a failed or "zombied" connection. The client should immediately terminate the connection and reconnect.
Example Heartbeat
{ "op": "heartbeat" }
Example Heartbeat ACK
{ "op": "heartbeat_ack" }
Finalizing
Once the mobile client creates a remote auth session (scans the QR code), the Gateway will send an Opcode Pending Ticket payload, which contains the information of the user that is attempting to authenticate.
Pending Ticket Structure
Field | Type | Description |
---|---|---|
encrypted_user_payload | string | The base64-encoded user payload encrypted with the client's public key |
User Payload Structure
The user payload is a colon-separated string in the format 852892297661906993:0:05145cc5646fbcba277b6d5ea2030610:dolfies
.
It contains the following fields (see the user object for more information):
Field | Type | Description |
---|---|---|
id | snowflake | The ID of the user |
discriminator | string | The user's stringified 4-digit Discord tag |
avatar | ?string | The user's avatar hash |
username | string | The user's username, may be unique across the platform (2-32 characters) |
Example Pending Ticket
{"op": "pending_ticket","encrypted_user_payload": "MeJm9TeLa9S+/gUYZlm69TQqT3eqz1sG6f5Ym84lGCH7Hde/Dpv/knXX+wWp8WeyLPHYGn1smpTaGxwHmuvee1IZv8ybP9WeVscsBnrXpO7Fg2aT3hZJfPStncJxI0Uq+JhjqQ1V2lLuhoraeBrZbl/e0CBNhv0wmIGzFow6G078MQvikg20Jr+/2wRgY/buXuipqfW9RYIZUyr3Dl+MRW8EodKU3SOBgqaVpHETDLXkmb6rsvYU+O78iUu+cvTK3A0GkajxmhJ2nfg79iQBuLU6qJm7sN9mHy2uKAExa5TUJfeeCqXjTr1uROozlyBmCMjeP+Srrg37r0y2pkjYCg=="}
At this point, the desktop client will either receive an Opcode Pending Login or Opcode Cancel event.
Both of these events will result in the Gateway closing with a 1000
close code. If neither of these events occur (the mobile client does not finish or cancel), the Gateway will eventually timeout and close with a 4003
close code.
If the mobile client finishes remote auth, the Gateway will send an Opcode Pending Login payload, which indicates that the authentication was successful.
Pending Login Structure
Field | Type | Description |
---|---|---|
ticket | string | The ticket that can be used to obtain a token |
Example Pending Login
{"op": "pending_login","ticket": "ODUyODkyMjk3NjYxOTA2OTkz.HYoNwT.1X5Qs3Sd2Z2sDf3sFFFwd22_MccjcmwY"}
If the mobile client cancels remote auth, the Gateway will send an Opcode Cancel payload, which indicates that the authentication was canceled.
Example Cancel
{ "op": "cancel" }
Endpoints
Exchange Remote Auth Ticket
POST
/users/@me/remote-auth/login
Exchanges a remote auth ticket for an authentication token.
JSON Params
Field | Type | Description |
---|---|---|
ticket | string | The ticket obtained from the remote authentication flow |
Response Body
Field | Type | Description |
---|---|---|
encrypted_token | string | The authentication token encrypted with the client's public key |