Skip to content

Instantly share code, notes, and snippets.

@jonbarrow
Last active October 22, 2023 21:20
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonbarrow/5fec3abebff2a2613851a6ab2864d543 to your computer and use it in GitHub Desktop.
Save jonbarrow/5fec3abebff2a2613851a6ab2864d543 to your computer and use it in GitHub Desktop.
Discord remote login script example
/*
Discord is able to log users in by scanning QR codes on the login screen,
these are randomly generated by the server and are sent to the client
over a secure web socket as a fingerprint, which gets scanned in by the app
and then used to login remotely. The QR codes actual contents are just
https://discord.com/ra/FINGERPRINT
This script shows how to connect to Discord and generate these
fingerprints automatically
*/
const crypto = require('crypto');
const WebSocket = require('ws');
const remoteAuthGatewayUrl = 'wss://remote-auth-gateway.discord.gg/?v=1';
// Generate a new random RSA key pair for the connection
const rsaKeyPair = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048
});
// Export the public key as SPKI
const publicKey = rsaKeyPair.publicKey.export({
type: 'spki',
format: 'der'
});
// Connect to gateway, 'Origin' header must be set or connection refused
const socket = new WebSocket(remoteAuthGatewayUrl, [], {
headers: {
'Origin': 'https://discord.com',
}
});
socket.on('error', error => {
console.log(error);
});
socket.on('close', (code, reason) => {
console.log(reason.toString());
});
socket.on('message', handleMessage);
function handleMessage(data) {
const message = JSON.parse(data.toString());
switch (message.op) {
// Server has accepted our connection request
// Initialize fingerprint request
case 'hello':
// Send the server our public key
socket.send(JSON.stringify({
op: 'init',
encoded_public_key: publicKey.toString('base64')
}));
break;
// Server has accepted the public key and encrypted
// a proof of identity check with it. Decrypt the
// nonce and create the proof to prove it was our
// public key used
case 'nonce_proof':
const encryptedNonce = Buffer.from(message.encrypted_nonce, 'base64');
const decryptedNonce = crypto.privateDecrypt({
key: rsaKeyPair.privateKey,
oaepHash: 'sha256'
}, encryptedNonce);
// Proof is the SHA256 hash of the decrypted nonce
// as base64 with a different alphabet
const hash = crypto.createHash('sha256').update(decryptedNonce).digest('base64');
const proof = hash.replace(/\//g, '_').replace(/\+/g, '-').replace(/={1,2}$/, '');
// Send the server our proof
socket.send(JSON.stringify({
op: 'nonce_proof',
proof
}));
break;
// Server has accepted our proof and sent the new QR
// fingerprint
case 'pending_remote_init':
// Grab the fingerprint and close the socket
const { fingerprint } = message;
console.log(fingerprint);
socket.close();
break;
}
}
/*
This script shows how to use the fingerprint to login remotely using the
Discord API
*/
const got = require('got');
const token = 'USER AUTH TOKEN';
const fingerprint = 'FINGER PRINT FROM WEB SOCKET CONNECTION';
const urlRemoteAuth = 'https://discord.com/api/v9/users/@me/remote-auth';
const urlRemoteAuthFinish = 'https://discord.com/api/v9/users/@me/remote-auth/finish';
async function main() {
// Send the server the fingerprint to request a handshake token
const response = await got.post(urlRemoteAuth, {
headers: {
authorization: token
},
json: {
fingerprint
}
}).json();
const handshakeToken = response.handshake_token;
// Confirm the login request by sending the server back the handshake token.
// The user should not be logged in remotely
await got.post(urlRemoteAuthFinish, {
headers: {
authorization: token
},
json: {
handshake_token: handshakeToken,
temporary_token: false
}
});
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment