I’m still confused about why this is an issue? Can you elaborate more? All RoCheck does it return the IP of the game server that is handling the specified job ID. It is up to you to compare the IPs.
Oops, my bad. I had forgotten that you need to specify a job id. This makes my problem practically impossible. Thank you for any clarifications.
What would happen if you have multiple requests coming though? Would I need to find a way to iterate though multiple bot cookies?
As far as I can tell (and I’ve done a lot of testing), this scales perfectly fine with a single bot cookie with lots of requests. The bot never joins the game (it never initialises a UDP connection), so it can send multiple requests to the API at the same time.
Granted, I recommend setting up some sort of cookie pool system. This means you have extra redundancy should one cookie fail, and you won’t have to worry about cookies expiring. You can read more about it here: Cookie Pool System
Do you happen to have the code for the cookie pool system by any chance? I’m not great with working with Roblox’s API nevermind renewing the cookies.
I have already considered releasing the cookie pool source. Unfortunately, it is far too integrated with my project that I can’t really open source it very easily. I will give the bit of source for actually refreshing the cookie, but everything else (getting all the cookies in the cookie pool database and overwriting them, the cron job, etc) is up to you.
relog.js
/**
* Module used to reload a cookie
*/
const request = require('request-promise')
const getVerificationInputs = require('./getVerificationInputs').func
module.exports = {
/**
* Get the RequestVerificationToken
*
* @param {string} Cookie
*/
getVerification: cookie => {
return new Promise((resolve, reject) => {
return request({
url: 'https://www.roblox.com/my/account#!/security',
resolveWithFullResponse: true,
headers: {
cookie: `.ROBLOSECURITY=${cookie}`
}
}).then(res => {
const inputs = getVerificationInputs({ html: res.body })
var match
if (res.headers && res.headers['set-cookie']) {
match = res.headers['set-cookie']
.toString()
.match(/__RequestVerificationToken=(.*?);/)
}
resolve({
inputs: inputs,
header: match && match[1]
})
})
})
},
/**
* Get the general token
*
* @param {string} Cookie
*/
getGeneralToken: async cookie => {
return new Promise((resolve, reject) => {
return request({
// This will never actually sign you out because an X-CSRF-TOKEN isn't provided, only received
url: 'https://api.roblox.com/sign-out/v1', // REQUIRES https. Thanks for letting me know, ROBLOX...
resolveWithFullResponse: true,
method: 'POST',
headers: {
cookie: `.ROBLOSECURITY=${cookie}`
}
}).catch(res => {
var xcsrf = res.response.headers['x-csrf-token']
if (xcsrf) {
resolve(xcsrf)
} else {
reject('Did not receive X-CSRF-TOKEN')
}
})
})
},
/**
* Reload a cookie
*
* @param {string} Cookie
*/
relog: cookie => {
return new Promise(async (resolve, reject) => {
if (!cookie) reject('no cookie supplied?')
// Get verification token
const verificationToken = await module.exports.getVerification(
cookie
)
if (!verificationToken.header) return reject('Bad cookie')
// Get general token
const generalToken = await module.exports.getGeneralToken(cookie)
// Refresh the token
return request({
url:
'https://www.roblox.com/authentication/signoutfromallsessionsandreauthenticate',
method: 'POST',
resolveWithFullResponse: true,
headers: {
'X-CSRF-TOKEN': generalToken,
cookie: `.ROBLOSECURITY=${cookie}`
},
form: {
__RequestVerificationToken:
verificationToken.inputs.__RequestVerificationToken
}
})
.then(res => {
const cookies = res.headers['set-cookie']
if (cookies) {
const newCookie = cookies
.toString()
.match(/\.ROBLOSECURITY=(.*?);/)[1]
resolve(newCookie)
} else {
reject('Bad Roblox response')
}
})
.catch(() => {
reject('Bad Roblox response')
})
})
}
}
getVerificationInputs.js
// Dependencies
var parser = require('cheerio')
// Define
exports.func = function(args) {
var $ = args.selector
if (!$) {
$ = parser.load(args.html)
}
var inputs = {}
var find = [
'__VIEWSTATE',
'__VIEWSTATEGENERATOR',
'__EVENTVALIDATION',
'__RequestVerificationToken'
]
for (var i = 0; i < find.length; i++) {
var get = find[i]
inputs[get] = $('input[name=' + get + ']').val()
}
return inputs
}
I have to give credit where credit is due, parts of this code is taken from noblox.js.
Looks great! Thanks, perhaps instead of open sourcing it you could release a post on your methods and what you did to get to that point and let others do it from there.
What advantages does this have over having a secret API key that the Roblox game server only ever knows (no client gets a copy of it) and then validating all HTTP requests by making sure they include that exact string?
Using an API key in that way isn’t providing 100% security to your API someone could still send you an API request with a string that so happens to be your API key and now they have access to your API and can do huge dangers. This not only allows you to verify that it’s coming from a Roblox server but also to verify that it’s coming from your authorized place ID.
If you keep that API key hidden on your server, the chances of anyone getting the exact string and sending malicious requests should be next to zero. That’s not really a valid concern if you maintain proper security - which isn’t even very intensive.
You don’t want to take any chances that you don’t need to. Security of your API is high. Just because the chances are next to zero doesn’t mean take the precautions needed in order to protect your API. If google were to have a way to protect their data and API better right infront of them but said “Hey the chances of our API getting abused is next to zero” would you feel safe giving them any of your data? The same goes with this you always want a completely protected API with extra layers of security.
I can 100% guarantee you that the way Google authenticates its requests is through security key strings like I described above. It’s an industry-standard method.
But that’s not really the point. I’m just wondering what advantages this has? It seems expensive and like it has some major drawbacks (doesn’t work with full servers). It’s really cool what you’ve made, but I think you may be better off saving your server expenses by implementing a cheaper, easier method like a security key system.
As stated in my previous post:
- Ability to verify it’s actually coming from a Roblox Server
- Ability to verify that the roblox server it’s coming from’s placeID is allowed to use your API.
- Added security.
Oh woops… Looks like my API key got leaked by one of my past developers. Well that sucks cause running this request will give you all my secure loadstring code. Looks like I’ll have to go reset everything instead.
API Key isn’t secure for everything.
I get that this gives that - but so does a strong security key method. A 64-character string that is protected by never being sent to a client is going to give you a 99.99999999999999999% guarantee that it is both coming from a Roblox server and, better than that, coming from a Roblox server running your game. And it’s far cheaper and faster than running a bot to join a server that could be full.
- Don’t leak your API key.
- Put your API Key into the data store for instant changes, if they’re needed.
- Build a multi-layer security key system so that you’d have to accidentally leak more than one string.
- Don’t leak your API key.
Doesn’t account for someone else leaking it say someone who worked on a game with me.
Datastore would need to be constantly checked and the server would have to provide a specific response that would tell the roblox game to check for a new key.
Logging onto Roblox uses an API key in a way for your account - your cookie string. All a cookie is is a string, and in the case of an authentication token, it’s just a string that lets the servers validate that the request is coming from a particular user.
Don’t hire people you can’t trust.