Session API
Endpoints para criar sessões de jogo e validar tokens JWT
Visão Geral
A Session API possui dois endpoints:
| Método | Path | Auth HMAC | Descrição |
|---|---|---|---|
| POST | /api/operator/session/create | Sim | Cria uma sessão e retorna o token |
| POST | /api/operator/session/validate | Não | Valida um token e retorna seus claims |
Base URL: https://mines.fourplay.studio
POST /api/operator/session/create
Cria uma sessão de jogo para um jogador. Retorna um JWT que o jogador usa para acessar o jogo.
Request
Headers:
| Header | Obrigatório | Valor |
|---|---|---|
Content-Type | Sim | application/json |
X-Operator-Id | Sim | Sua API Key (op_live_...) |
X-Timestamp | Sim | Timestamp em milissegundos |
X-Signature | Sim | HMAC-SHA256 calculado |
Body:
{
"externalPlayerId": "player_123",
"username": "João Silva"
}| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
externalPlayerId | string | Sim | ID do jogador no seu sistema (identificador único) |
username | string | Não | Nome de exibição do jogador no jogo |
Response
200 OK:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwbGF5ZXJJZCI6IjAxOTM0..."
}| Campo | Tipo | Descrição |
|---|---|---|
token | string | JWT de sessão — use para construir a URL do jogo |
Após receber o token, construa a URL do jogo:
https://mines.fourplay.studio/?token={token}Erros possíveis:
| HTTP | Corpo | Causa |
|---|---|---|
| 400 | { "message": "Missing required fields" } | externalPlayerId ausente no body |
| 401 | { "message": "Invalid signature" } | HMAC incorreto, timestamp fora da janela de 5 min |
| 401 | { "message": "Operator not found" } | API Key não reconhecida |
| 403 | { "message": "Operator is inactive" } | Conta de operador inativa ou suspensa |
Exemplos Completos
#!/bin/bash
API_KEY="op_live_..."
SECRET_KEY="sk_live_..."
BODY='{"externalPlayerId":"player_123","username":"João Silva"}'
TIMESTAMP=$(date +%s%3N)
DATA="${API_KEY}${TIMESTAMP}${BODY}"
SIGNATURE=$(echo -n "$DATA" | openssl dgst -sha256 -hmac "$SECRET_KEY" | cut -d' ' -f2)
curl -s -X POST "https://mines.fourplay.studio/api/operator/session/create" \
-H "Content-Type: application/json" \
-H "X-Operator-Id: $API_KEY" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE" \
-d "$BODY"
# Resposta: {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}const crypto = require('crypto');
const API_KEY = process.env.FOURPLAY_API_KEY;
const SECRET_KEY = process.env.FOURPLAY_SECRET_KEY;
async function createSession(externalPlayerId, username) {
const payload = { externalPlayerId, ...(username && { username }) };
const body = JSON.stringify(payload);
const timestamp = Date.now().toString();
const data = API_KEY + timestamp + body;
const signature = crypto.createHmac('sha256', SECRET_KEY).update(data).digest('hex');
const res = await fetch('https://mines.fourplay.studio/api/operator/session/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Operator-Id': API_KEY,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body,
});
const result = await res.json();
if (!res.ok) throw new Error(result.message ?? 'Session creation failed');
return result; // { token: "eyJ..." }
}
// Uso em Express:
app.get('/play', async (req, res) => {
const { token } = await createSession(req.user.id, req.user.name);
res.redirect(`https://mines.fourplay.studio/?token=${token}`);
});<?php
function createSession(string $externalPlayerId, ?string $username = null): string {
$apiKey = getenv('FOURPLAY_API_KEY');
$secretKey = getenv('FOURPLAY_SECRET_KEY');
$payload = ['externalPlayerId' => $externalPlayerId];
if ($username !== null) $payload['username'] = $username;
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
$timestamp = (string)(int)(microtime(true) * 1000);
$data = $apiKey . $timestamp . $body;
$signature = hash_hmac('sha256', $data, $secretKey);
$ch = curl_init('https://mines.fourplay.studio/api/operator/session/create');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"X-Operator-Id: {$apiKey}",
"X-Timestamp: {$timestamp}",
"X-Signature: {$signature}",
],
]);
$raw = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($raw, true);
if ($httpCode !== 200) {
throw new RuntimeException($result['message'] ?? 'Session creation failed');
}
return $result['token'];
}
// Uso:
$token = createSession($_SESSION['user_id'], $_SESSION['user_name']);
$gameUrl = "https://mines.fourplay.studio/?token={$token}";
header("Location: {$gameUrl}");
exit;import hmac, hashlib, time, json, os
import urllib.request
API_KEY = os.environ['FOURPLAY_API_KEY']
SECRET_KEY = os.environ['FOURPLAY_SECRET_KEY']
def create_session(external_player_id: str, username: str | None = None) -> str:
payload = {'externalPlayerId': external_player_id}
if username:
payload['username'] = username
body = json.dumps(payload, separators=(',', ':'))
timestamp = str(int(time.time() * 1000))
data = API_KEY + timestamp + body
signature = hmac.new(SECRET_KEY.encode(), data.encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request(
'https://mines.fourplay.studio/api/operator/session/create',
data=body.encode(),
headers={
'Content-Type': 'application/json',
'X-Operator-Id': API_KEY,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
method='POST',
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())['token']
token = create_session('player_123', 'João Silva')
game_url = f'https://mines.fourplay.studio/?token={token}'using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
public class FourPlayClient(string apiKey, string secretKey, HttpClient http)
{
public async Task<string> CreateSessionAsync(string externalPlayerId, string? username = null)
{
var payload = username != null
? (object)new { externalPlayerId, username }
: new { externalPlayerId };
var body = JsonSerializer.Serialize(payload);
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
var data = apiKey + timestamp + body;
using var mac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey));
var sig = Convert.ToHexString(mac.ComputeHash(Encoding.UTF8.GetBytes(data))).ToLower();
var req = new HttpRequestMessage(HttpMethod.Post,
"https://mines.fourplay.studio/api/operator/session/create")
{
Content = new StringContent(body, Encoding.UTF8, "application/json")
};
req.Headers.Add("X-Operator-Id", apiKey);
req.Headers.Add("X-Timestamp", timestamp);
req.Headers.Add("X-Signature", sig);
var resp = await http.SendAsync(req);
var json = await resp.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<JsonElement>(json);
if (!resp.IsSuccessStatusCode)
throw new Exception(result.TryGetProperty("message", out var msg)
? msg.GetString() : "Session creation failed");
return result.GetProperty("token").GetString()!;
}
}
// Uso em ASP.NET:
var token = await fourplayClient.CreateSessionAsync(user.Id, user.Name);
var gameUrl = $"https://mines.fourplay.studio/?token={token}";
return Redirect(gameUrl);POST /api/operator/session/validate
Valida um token JWT e retorna os claims internos. Útil para inspecionar uma sessão existente.
Este endpoint não requer autenticação HMAC — apenas o token no body.
Request
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
token | string | Sim | JWT de sessão |
Response
200 OK:
{
"playerId": "01934c2e-789b-7000-bf1d-4f86ee0d6721",
"operatorId": "01934c2e-5a1f-7000-9b3e-2d7c89f1a234",
"username": "João Silva",
"isDemo": false
}| Campo | Tipo | Descrição |
|---|---|---|
playerId | string | UUID interno do jogador no banco do Mines |
operatorId | string | UUID interno do operador no banco do Mines |
username | string? | Nome de exibição (apenas se enviado na criação) |
isDemo | bool? | true para sessões de demonstração |
Erros possíveis:
| HTTP | Corpo | Causa |
|---|---|---|
| 400 | { "message": "Token is required" } | Token ausente no body |
| 401 | { "message": "Invalid token" } | Token malformado ou expirado |
Exemplos
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -s -X POST "https://mines.fourplay.studio/api/operator/session/validate" \
-H "Content-Type: application/json" \
-d "{\"token\":\"$TOKEN\"}"async function validateSession(token) {
const res = await fetch('https://mines.fourplay.studio/api/operator/session/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
});
const result = await res.json();
if (!res.ok) throw new Error(result.message ?? 'Validation failed');
return result; // { playerId, operatorId, username?, isDemo? }
}<?php
function validateSession(string $token): array {
$ch = curl_init('https://mines.fourplay.studio/api/operator/session/validate');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['token' => $token]),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
$raw = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($raw, true);
if ($httpCode !== 200) {
throw new RuntimeException($result['message'] ?? 'Validation failed');
}
return $result;
}import json, urllib.request
def validate_session(token: str) -> dict:
body = json.dumps({'token': token}).encode()
req = urllib.request.Request(
'https://mines.fourplay.studio/api/operator/session/validate',
data=body,
headers={'Content-Type': 'application/json'},
method='POST',
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())async Task<JsonElement> ValidateSessionAsync(string token)
{
var body = JsonSerializer.Serialize(new { token });
var req = new HttpRequestMessage(HttpMethod.Post,
"https://mines.fourplay.studio/api/operator/session/validate")
{
Content = new StringContent(body, Encoding.UTF8, "application/json")
};
var resp = await http.SendAsync(req);
var json = await resp.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<JsonElement>(json);
if (!resp.IsSuccessStatusCode)
throw new Exception(result.TryGetProperty("message", out var msg)
? msg.GetString() : "Validation failed");
return result;
}Notas Importantes
Token JWT (24 horas)
O token expira 24 horas após a criação. O jogo detecta automaticamente a expiração. Se o jogador quiser jogar após a expiração, o operador deve criar uma nova sessão.
Decodificando o JWT localmente
Se precisar inspecionar o token sem chamar o endpoint /validate:
function decodeJwt(token) {
const [, payload] = token.split('.');
return JSON.parse(Buffer.from(payload, 'base64url').toString());
}
const claims = decodeJwt(token);
// { playerId, operatorId, username?, isDemo?, iat, exp }Decodificar localmente não valida a assinatura. Use apenas para leitura de claims — nunca para autorização.