Skip to content

Signed Requests

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

Signed requests in Microservices

A Microservice exposes the request-scoped this.SignedRequester. Pass it to generated API constructors to make signed requests in the Microservice's current realm. The following admin-only method finds an account by player ID (gamerTag), then returns that player's public game stats:

For a locally running Microservice, enable realm secret authentication in Program.Main before starting the service:

await BeamServer.Create()
    .IncludeRoutes<PlayerStatsService>(routePrefix: "")
    .UseRealmSecretAuth()
    .RunForever();

UseRealmSecretAuth() makes local environment generation include the current realm's secret. Without it, this.SignedRequester can send a request with an invalid signature and the gateway returns 403 InvalidSignatureError. Restart the local Microservice after changing its startup configuration. The account running it must have permission to retrieve the realm secret.

using System.Threading.Tasks;
using Beamable.Api.Autogenerated.Accounts;
using Beamable.Api.Autogenerated.Models;
using Beamable.Api.Autogenerated.Stats;
using Beamable.Server;

public class PlayerStatsService : Microservice
{
    [AdminOnlyCallable]
    public async Task<StatsResponse> GetPublicGameStats(long userId)
    {
        var requester = this.SignedRequester;
        await new AccountsApi(requester).GetFind(userId.ToString());
        return await new StatsApi(requester).ObjectGet($"game.public.player.{userId}");
    }
}

Use the same constructor pattern for other generated APIs. For example, inside an admin-only method that receives long userId, these calls read the player's inventory and Cloud Save manifest:

var requester = this.SignedRequester;
var inventory = await new InventoryApi(requester).ObjectGet(userId);
var manifest = await new CloudsavingApi(requester)
    .Get(userId, false)
    .RecoverFrom404(_ => new CloudsavingBasicManifest());

Add Beamable.Api.Autogenerated.Inventory, Beamable.Api.Autogenerated.Cloudsaving, and Beamable.Common.Api to the example's using directives for the second snippet. RecoverFrom404 is optional; it supplies an empty manifest when the player has no Cloud Save manifest. The false argument to Get omits a player authentication header, while the requester still signs the call.

The built-in requester signs for the current realm. Passing userId to these API methods selects a player in that realm; it does not switch the request's player context or sign for another realm. Keep signed-request operations behind an admin-only callable because the signature carries realm-level privileges.

Calculating Signatures

The signature for any given request is a one-way hash of five pieces of information:

  1. Realm Secret (a UUID)
  2. The PID of the realm
  3. API version string; at present the version is always "1"
  4. The path part of the URL including its leading slash (for example: /basic/tournaments/rewards)
  5. 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)