Server-to-Server: your backend gets its own login, and a raw request format
Cloud Code, the pattern is the client-triggered equivalent of trusted server logic. This article is for when the trusted logic lives on a server you run yourself, outside brainCloud. Room Servers is the concrete case where you'll use this the most.
Everything in this series so far has been client code talking to brainCloud through a player's session. S2S is a different door: a backend authenticates as a server, not as a player, and gets full access to brainCloud's services through one generic request format. There's no per-service method wrapper the way the client SDKs have ScriptService.RunScript or LobbyService.findOrCreateLobby. A JSON object names the service and operation, and gets sent as-is. cpp-s2s's test suite and the csharp-examples/RSM room server both use exactly this pattern, so it's well proven across two languages.
How it works
- Create a context with server credentials, not a player login. S2SContext::create(appId, serverName, serverSecret, serverUrl, autoAuth) in C++, or BrainCloudS2S.Init(appId, serverName, serverSecret, useShared, s2sUrl) in C#. These are app-level secrets issued for a specific server identity, not a player's email or anonymous id.
- Decide up front whether auto-auth is wanted. That last boolean matters more than it looks. With auto-auth off, authenticateSync() (or Authenticate(callback)) must be called before sending any request, and re-authenticating after the session expires is the caller's responsibility. With auto-auth on, the library handles that. cpp-s2s runs its entire test suite twice, once with each setting, because the behavior differs enough to be worth testing separately.
- Every call has the same generic format: service, operation, data. There's no getLobbyService()->something(). A request like {"service": "lobby", "operation": "GET_LOBBY_DATA", "data": {"lobbyId": lobbyId}} gets sent as that whole object. The service and operation names come from brainCloud's API reference, the same names a client SDK method wraps internally, just exposed directly here instead of hidden behind a method call.
- Check status on the response directly. A raw S2S response comes back as JSON with a status field. There's no typed success or failure callback deciding that the way client SDK calls do. A status other than 200 should be treated as a failure and branched on accordingly.
Code
auto pContext = S2SContext::create(
BRAINCLOUD_APP_ID,
BRAINCLOUD_SERVER_NAME,
BRAINCLOUD_SERVER_SECRET,
BRAINCLOUD_SERVER_URL,
false
);
REQUIRE(pContext);auto pContext = S2SContext::create(
BRAINCLOUD_APP_ID,
BRAINCLOUD_SERVER_NAME,
BRAINCLOUD_SERVER_SECRET,
BRAINCLOUD_SERVER_URL,
true
);
REQUIRE(pContext);bool runAuth(S2SContextRef pContext)
{
std::string ret = pContext->authenticateSync();
Json::Value data;
Json::Reader reader;
bool parsingSuccessful = reader.parse(ret.c_str(), data);
if (!parsingSuccessful)
{
return false;
}
return data["status"].asInt() == 200;
}void RequestLobbyData()
{
var request = new Dictionary<string, object>
{
{ "service", "lobby" },
{ "operation", "GET_LOBBY_DATA" },
{ "data", new Dictionary<string, object>
{
{ "lobbyId", _lobbyId }
}}
};
_s2s.Request(request, OnLobbyData);
}auto request = "{ \
\"service\": \"script\", \
\"operation\": \"RUN\", \
\"data\": { \
\"scriptName\": \"AddTwoNumbers\" \
} \
}";Gotchas worth knowing up front
- Raw request JSON fails at runtime on a typo, not compile time. There's no IDE autocomplete to catch "service": "lobbby" the way a client SDK method call would. Check the exact service and operation strings against brainCloud's API reference before shipping a new call.
- Auto-auth changes error-handling responsibilities, not just setup code. With it off, an expired session on a long-running server process is a bug the caller must catch and recover from. With it on, the library covers that case, but the case where auto-auth itself fails still needs handling.
- This context is a server identity; treat it like any backend credential. S2SContext::create takes the app's server secret. That's not something to hand to a client build or check into a public repo.
- A 200 status on the transport doesn't mean the requested operation succeeded. The same "transport succeeded, check the response body" rule from cloud code (see Article 5) applies here too. Read the actual response payload, not just whether the callback fired.