Make privileged server-to-server Beamable API requests using signatures based on your realm secret
Beamable supports server-to-server privileged API requests in addition to token based access. In contrast to token based requests which must act as a particular Beamable user (who may or may not have administrative privileges), signed requests act as a server entity with universal privileges in its realm. This allows for server-authoritative action such as setting "game.private" stats, submitting scores to leaderboards whose "writeSelf" access is false, and so on.
In technical terms, signed requests differ from token based requests in several ways:
- Signed requests never have an
Authorization:
header; instead, they rely on the signature to prove their authorization - Signed requests must include a signature in the
X-BEAM-SIGNATURE:
header. The procedure for calculating the signature value is described below. - Signed requests may include a player ID in the
X-BEAM-GAMERTAG:
header; this is useful when using an API route that is normally player-oriented and usually gets the player ID implicitly from the access token.
Both token based requests and signed requests require the X-BEAM-SCOPE:
header to indicate which realm they are operating in. The format for the scope header is a dotted pair of CID (numeric organization ID) and PID (project ID, identifying the realm within the organization). Example: X-BEAM-SCOPE: 1434605640884224.DE_1434605640884225
Calculating Signatures
The signature for any given request is a one-way hash of five pieces of information:
- Realm Secret (a UUID)
- The PID of the realm
- API version string; at present the version is always "1"
- The path part of the URL including its leading slash (for example:
/basic/tournaments/rewards
) - The exact body of the request, if it has one
To calculate the signature, concatenate these five strings, take the digest of the MD5 hash of the concatenated plaintext, and Base64-encode it. This should result in a Base64 string a couple dozen characters long.
Code Examples: Calculate Signed Request Signature
public class BeamableSignedRequester
{
private HttpClient _client;
private string _cid, _pid, _secret;
public BeamableSignedRequester(string cid, string pid, string secret, HttpClient client = null)
{
_cid = cid;
_pid = pid;
_secret = secret;
_client = client ?? new HttpClient();
}
/// <summary>
/// Calculate the Beamable signature for a given request. This signature
/// can be used in the X-BEAM-SIGNATURE header of a HTTP request to make
/// a privileged request to the Beamable API.
/// </summary>
/// <param name="pid">Project ID of the realm ("DE_...")</param>
/// <param name="secret">The realm secret, which is a UUID</param>
/// <param name="uriPathAndQuery">Path and query parts of the URI</param>
/// <param name="body">Request body, if any</param>
/// <returns>Signature as a short Base64 string</returns>
public static string CalculateSignature(string pid, string secret, string uriPathAndQuery, string body = null)
{
const string version = "1";
var md5 = System.Security.Cryptography.MD5.Create();
var dataToSign = $"{secret}{pid}{version}{uriPathAndQuery}";
if (body != null) dataToSign += body;
byte[] data = Encoding.UTF8.GetBytes(dataToSign);
byte[] hash = md5.ComputeHash(data);
return Convert.ToBase64String(hash);
}
public string CalculateRequestSignature(string uriPathAndQuery, string body = null) =>
CalculateSignature(_pid, _secret, uriPathAndQuery, body);
public async Task<HttpResponseMessage> Request(HttpMethod method, Uri uri, string body = null)
{
var signature = CalculateRequestSignature(uri.PathAndQuery, body);
var request = new HttpRequestMessage(method, uri);
request.Headers.Add("X-BEAM-SCOPE", $"{_cid}.{_pid}");
request.Headers.Add("X-BEAM-SIGNATURE", signature);
if (body != null)
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await _client.SendAsync(request);
return response;
}
}
"""
signedrequest - Make Beamable signed requests using the realm secret.
"""
import os
import sys
from base64 import b64encode
from hashlib import md5
REALM_SECRET = os.environ['REALM_SECRET']
BEAM_PID = os.environ['PID']
API_VERSION = '1'
def signature(url: str, body: str) -> str:
plaintext = (REALM_SECRET + BEAM_PID + API_VERSION + url + body).encode()
md5hash = md5(plaintext)
digest = md5hash.digest()
base64bytes = b64encode(digest)
return base64bytes.decode('utf-8')
def main(args):
url = args[1]
body = args[2] if len(args) > 2 else ''
sig = signature(url, body)
print(sig)
if __name__ == '__main__':
main(sys.argv)