Oturumlar ve CSRF
Giriş yapan oyuncuya bir oturum verilir. Bu sayfa oturumun nasıl taşındığını, ne kadar yaşadığını, nasıl yenilenip kapatıldığını ve durum değiştiren isteklerde zorunlu olan CSRF (çapraz site istek sahteciliği) korumasını anlatır. SDK kullanıyorsanız çoğunu sizin yerinize yönetir; ham REST yazıyorsanız bu sayfa sözleşmedir.
Oturum nasıl taşınır?
Sunucu oturumu iki yerden birinden okur; önce çerez, yoksa Authorization başlığı:
| Kanal | Kim kullanır? | Biçim |
|---|---|---|
| Çerez | Tarayıcı oyunları | __Secure-crx.session=<64 hex> (HttpOnly; Secure; SameSite=Lax; Path=/) |
| Bearer | Çerezsiz istemciler (komut satırı, bazı yerel istemciler) | Authorization: Bearer <session_token> (giriş yanıtındaki opak belirteç) |
Her oyuncu isteği ayrıca x-crx-env: <ortam-kimliği> taşır. Bearer yolunda taşınan opak oturum belirtecidir; erişim JWT’si değildir (o, oyun sunucunuzun çevrimdışı doğrulaması içindir: Sunucuda doğrulama).
Oturumu okuma
GET /v1/auth/session
x-crx-env: <ortam-kimliği>
{
"environment_id": "0192…",
"player_id": "0192…",
"session_id": "0192…",
"is_guest": false,
"active_organization_id": null,
"displayName": "Ayşe",
"email": "[email protected]"
}
email misafir ve e-postasız (yalnız Google) hesaplarda null’dır. Yanıt kişisel veri taşır ve Cache-Control: private, no-store ile gelir. Oturum yoksa, iptal edilmişse, süresi dolmuşsa ya da hesap banlıysa 401 döner (ayrım sızmaz).
Süreler
| Öğe | Süre |
|---|---|
| Oturum ve yenileme penceresi | 30 gün, kayan |
| Mutlak üst sınır | 180 gün (oturum ailesinin başlangıcından; hiçbir yolla aşılmaz) |
| Erişim JWT’si | 15 dakika |
Çerez Max-Age | 15 552 000 sn (180 gün); yalnız üst sınırdır, karar sunucudadır |
| CSRF çerezi | 15 dakika |
- Kayma: oyuncu oturumla günde en az bir kez istek yaptığında oturum bitişi 30 gün ileri alınır (üst sınırı aşmadan). Her yenileme (refresh) de süreyi 30 güne çeker.
- 30 gün hiç istek yapmayan oturum ölür. 180. günde aktif oturum bile biter; oyuncu yeniden giriş yapar (misafir, aynı sırla aynı hesaba döner).
- Gerçek zamanlı bağlantıyı açık tutmak oturumu uzatmaz.
Yenileme (refresh)
POST /v1/auth/refresh
content-type: application/json
x-crx-env: <ortam-kimliği>
x-csrf-token: <csrf-belirteci>
{}
Yenileme belirteci gövdede ({"refresh_token":"…"}) ya da __Secure-crx.refresh çerezinde taşınır (gövde öncelikli; çerez varsa gövde boş olabilir). Yanıt 200 ve yeni oturum ve yenileme çifti (çerezler de yenilenir; ortak giriş zarfı). Eski yenileme belirteci kullanılmış sayılır.
- Yeniden kullanım tespiti: kullanılmış bir yenileme belirtecinin tekrar sunulması tüm oturum ailesini iptal eder (çalıntı belirteç savunması).
- Hata:
403(belirteç geçersiz, süresi dolmuş, yeniden kullanılmış ya da CSRF reddi). - SDK’lar yenilemeyi gerektiğinde kendisi yapar; siz çağırmazsınız.
Çıkış
POST /v1/auth/logout
content-type: application/json
x-crx-env: <ortam-kimliği>
x-csrf-token: <csrf-belirteci>
{}
204; oturum iptal edilir ve çerezler silinir (Max-Age=0). Çağrı idempotenttir: bilinmeyen belirteç sessizce başarılı olur. Yenileme belirteci hiç yoksa (çerez ve gövde boşsa) 400 döner.
Parola değiştirme, parola sıfırlama ve operatör banı da oturumları iptal eder; ilgili sayfalara bakın.
Tek aktif oturum (isteğe bağlı)
Panelde Oyun ayarları › Giriş yöntemleri › Giriş ayarları › Tek aktif oturum anahtarı (yalnız süper yönetici), ortam başına açılır. Açıkken oyuncuya yeni oturum veren her giriş yolu, yeni oturum yazıldıktan sonra oyuncunun diğer oturumlarını iptal eder (“en son giriş kazanır”).
- Anahtar açıldığı anda açık oturumları kapatmaz; yalnız bir sonraki girişten itibaren uygulanır.
- Yenileme (refresh) bu politikayı tetiklemez.
- Diğer cihazdaki oturum bir sonraki istekte
401alır; SDK durumureauthRequiredolur. Oyununuz bu durumda “başka bir cihazdan giriş yapıldı” gibi bir ekran gösterebilir.
CSRF çift gönderim
Çerezle taşınan oturum, tarayıcı tarafından başka sitelerin isteklerine de otomatik eklenir. Bunu önlemek için durum değiştiren isteklerde çift gönderim (double-submit) CSRF koruması uygulanır: aynı belirteç hem çerezde hem başlıkta gelmelidir.
Kapsam. Aşağıdaki isteklerde zorunludur: /v1/auth/* altındaki tüm GET dışı istekler (giriş uçları dahil); oyuncu oturumuyla çağrılan eklenti komutları (ve POST ile çağrılan sorgular); oyuncu mağaza satın alma (yalnız çerez oturumunda; Authorization: Bearer oturumunda yalnız Origin ve Fetch Metadata denetimi uygulanır); gerçek zamanlı bilet isteği. GET istekleri muaftır. Sunucu anahtarıyla yapılan çağrılar CSRF’ye tabi değildir.
Akış.
GET /v1/auth/csrfçağrılır. Yanıt: gövde{"token":"<hex>"}, başlıkx-csrf-token: <aynı belirteç>veSet-Cookie: __Secure-crx.csrf=<mühürlü grant>.- Her durum değiştiren istek, çerezi (
__Secure-crx.csrf) ve başlığı (x-csrf-token) birlikte gönderir. Tarayıcı çerezi kendisi ekler; başlığı kodunuz ekler. - Başarılı her istekte belirteç döner (rotate): yanıt, yeni
x-csrf-tokenbaşlığı ve yeniSet-Cookietaşır. Bir sonraki istekte yenisini kullanın. - Belirteç tek kullanımlıktır. Eski belirteçle tekrar gönderilen istek
403alır (yeniden oynatma koruması).
# Belirteç al
TOKEN="$(curl -sS -c "$JAR" "$OPEN_PLAY_URL/v1/auth/csrf" | jq -r .token)"
# Kullan; yanıt başlığındaki yeni belirteci bir sonraki istek için sakla
curl -sS -D headers.txt -b "$JAR" -c "$JAR" -X POST "$OPEN_PLAY_URL/v1/auth/logout" \
-H "content-type: application/json" -H "x-crx-env: $OPEN_PLAY_ENV" -H "x-csrf-token: $TOKEN" -d '{}'
TOKEN="$(grep -i '^x-csrf-token:' headers.txt | awk '{print $2}' | tr -d '\r')"
Dikkat edilecekler:
- Eşzamanlı iki mutasyonu aynı belirteçle göndermeyin; biri başarılı olunca diğeri
403alır. İstekleri sıraya koyun. SDK’lar bunu tek bir kilitle yapar. - CSRF çerezi 15 dakika yaşar; süresi dolunca
403alırsınız. Taze belirteç alıp bir kez yeniden deneyin.403düz metindir ve CSRF ile yetki reddi arasında ayrım yapmaz; yanıtta yeni birx-csrf-tokenbaşlığı gelmediyse bunun CSRF katmanı reddi olduğunu anlarsınız. - Origin denetimi CSRF’nin ilk katmanıdır: tarayıcı isteğinin
Originalan adı ortama bağlı olmalıdır. Bkz. Mimari ve barındırma. - Çerezsiz (Bearer) istemciler de
/v1/auth/*ve eklenti uçlarında CSRF çerezini ve başlığını taşımalıdır (mağaza satın almadaki Bearer kolu istisnadır). Unity SDK’sı bunu yerel derlemede çerezleri kendisi yöneterek, WebGL’de tarayıcı çerezleriyle yapar.
SDK ve oturum durumları
TypeScript session.state.status değerleri:
| Durum | Anlamı |
|---|---|
signedOut | Oturum yok |
authenticating | Giriş ya da geri yükleme sürüyor |
authenticated | Oturum açık; playerId, sessionId, isGuest, displayName, email dolu |
refreshing | Yenileme sürüyor |
reauthRequired | Oturum kaybedildi ya da yenilenemedi; yeniden giriş gerekir |
signingOut / logoutUncertain | Çıkış sürüyor / sonucu belirsiz (yeniden denenebilir) |
banned | Hesap banlı; terminal |
Unity’de karşılığı SessionStatus (SignedOut, Authenticating, Authenticated, Refreshing, ReauthRequired, SigningOut, LogoutUncertain, Banned, Disposed) değerleridir; durum değişimleri Session.StateChanged olayıyla gelir.
import { CrxError, ErrorKind, createPlayerClient, type PlayerClient, type SessionState } from "@ferdiunal/open-play-ts";
/** Oturum durumunu arayüze bağlar; `state.status` değerleri kapalı bir kümedir. */
export function watchSession(client: PlayerClient, render: (label: string) => void): () => void {
const label = (state: SessionState): string => {
switch (state.status) {
case "authenticated": return state.isGuest === true ? "Misafir" : (state.displayName ?? "Oyuncu");
case "banned": return "Hesap askıya alındı";
case "reauthRequired": return "Yeniden giriş gerekiyor";
case "signedOut": return "Giriş yapılmadı";
default: return "…"; // authenticating, refreshing, signingOut, logoutUncertain
}
};
render(label(client.session.state));
return client.session.subscribe((state) => render(label(state))); // dönen işlev aboneliği kapatır
}
/** Sayfa kapanırken: sunucu oturumunu kapatmak için `logout`, yalnız yerel kaynakları bırakmak için `dispose`. */
export async function leave(client: PlayerClient, signOut: boolean): Promise<void> {
try {
if (signOut) await client.session.logout();
} catch (error) {
if (!(error instanceof CrxError && error.kind === ErrorKind.UnknownOutcome)) throw error;
// Yanıt kaybolduysa sunucu oturumu kapanmamış olabilir (durum `logoutUncertain`); gerekirse logout'u yeniden deneyin.
} finally {
await client.dispose();
}
}
export function createClient(environmentId: string): PlayerClient {
return createPlayerClient({ baseUrl: window.location.origin, environmentId });
}
dispose() yalnızca yerel kaynakları bırakır; sunucu oturumunu kapatmak için önce logout() çağırın. AuthenticatedSession.email yalnız oyuncunun kendi ekranı içindir; günlüğe ve analitiğe yazmayın.