Авторизация в Яндекс Играх устроена не так, как на большинстве платформ. Здесь нет своей регистрации, нет формы «введите email», и главное — вход не обязателен. Игрок может открыть игру, пройти десять уровней, закрыть вкладку, вернуться завтра и увидеть свой прогресс, так и не авторизовавшись. Это не побочный эффект, а требование платформы: игра, которая заставляет войти в аккаунт, отклоняется на модерации.
В этой главе разберём объект Player: как его получить, как отличить гостя от авторизованного пользователя, как корректно открыть окно входа и что именно проверяет модерация в пункте 1.2. Все факты сверены с официальной документацией в сентябре 2026 года — ссылки на источники в шапке главы.
Что даёт авторизация, а что работает и без неё
Платформа сама сохраняет игровой прогресс и авторизованных, и неавторизованных игроков — облачные сохранения через player.setData() работают в обоих случаях. Поэтому «сохранение прогресса» само по себе плохой аргумент за вход: гость его тоже получает, просто привязанный к текущему браузеру.
Реальная выгода от авторизации, о которой честно можно написать в интерфейсе:
- прогресс переносится между устройствами и браузерами, а не живёт в одном;
- купленные предметы и улучшения доступны везде, а не только там, где их купили (для игр с инап-покупками облачные сохранения обязательны по пункту 1.13.3);
- становятся доступны лидерборды —
leaderboards.setScore()иgetPlayerEntry()требуют авторизации; - в игре появляются имя и аватар игрока.
Без авторизации остаётся доступным: сам факт запуска, уникальный идентификатор игрока, сохранение и чтение данных, реклама, чтение таблицы лидеров через getEntries().
Требование 1.2: правила, из-за которых игру отклоняют
Пункт 1.2 требований формулируется коротко: для запуска и использования игры не нужна регистрация или авторизация на сторонних сервисах, допустима только авторизация с Яндекс ID. Дальше идут два подпункта, которые и ловят модераторы.
| Правило | Правильно | Неправильно |
|---|---|---|
| 1.2 Основные функции без входа | Играть можно сразу, вход опционален | Игра начинается только после авторизации |
| 1.2 Только Яндекс ID | Кнопка ведёт на ysdk.auth.openAuthDialog() | Своя форма регистрации, вход через VK, Google, Telegram |
| 1.2.1 Осознанное действие | Вход открывается по кнопке «Войти» | Окно входа открывается по кнопке «Лидерборд» или «Оценить игру» |
| 1.2.1 Объяснённая выгода | Рядом с кнопкой написано, что даёт вход | Кнопка «Войти» без пояснений |
| 1.2.1 Право отказаться | Есть кнопка «Позже» / «Играть без входа» | Модальное окно без выхода |
| 1.2 Не повторяться | После входа предложение исчезает | Предложение авторизоваться показывается уже вошедшему игроку |
| 1.2.2 Гостевой прогресс | Гость тоже сохраняется | Прогресс сохраняется только авторизованным |
Важно
Самая частая ошибка — вешать openAuthDialog() на кнопку лидерборда. Логика понятна: рекорд без авторизации не отправить. Но с точки зрения пользователя он нажал «Таблица рекордов», а получил экран входа в Яндекс. По документации это прямой пример неправильной реализации 1.2.1. Правильный путь: показать таблицу (getEntries() работает без авторизации), а под ней — отдельную кнопку «Войти, чтобы попасть в рейтинг».
Объект Player: ysdk.getPlayer()
Объект Player — точка входа ко всему, что связано с игроком: идентификатор, профиль, сохранения. Получается он одним асинхронным вызовом после инициализации SDK.
const ysdk = await YaGames.init();
const player = await ysdk.getPlayer();При инициализации платформа передаёт в игру идентификатор пользователя (всем), а имя и аватар — только авторизованным. Доступ к персональным данным зависит ещё и от настроек профиля самого пользователя: если он запретил их передачу, в ответе будет только идентификатор, даже когда игрок авторизован. Поэтому имя из getName() всегда нужно подстраховывать запасным значением.
Единственная опция — signed
getPlayer() принимает ровно один документированный параметр — { signed: true }. Он нужен, если вы проверяете подлинность игрока на своём сервере (об этом в конце главы).
// Клиентская игра без своего сервера — параметры не нужны.
const player = await ysdk.getPlayer();
// Игра со своим сервером: в объекте появится player.signature.
const signedPlayer = await ysdk.getPlayer({ signed: true });Частая ошибка
В старых туториалах и в коде, который генерируют языковые модели, часто встречается ysdk.getPlayer({ scopes: true }) — якобы «запросить доступ к имени и аватару». Слова scopes в актуальной документации SDK нет вообще (проверено в русской и английской версиях в сентябре 2026 года). Считайте этот параметр легаси: он ничего не гарантирует, доступ к профилю определяется фактом авторизации и настройками пользователя. Пишите getPlayer() или getPlayer({ signed: true }) — других вариантов документация не описывает.
Проверка статуса и окно входа
Статус проверяется синхронным методом player.isAuthorized() — он возвращает true или false. Окно входа открывается методом ysdk.auth.openAuthDialog(), который возвращает промис.
Ключевой момент, который ломает половину реализаций: после успешной авторизации старый объект Player не становится авторизованным. Он остаётся гостевым навсегда. Нужно запросить getPlayer() заново и заменить ссылку.
let player = await ysdk.getPlayer();
if (!player.isAuthorized()) {
try {
await ysdk.auth.openAuthDialog();
// Обязательный шаг: перечитываем игрока.
player = await ysdk.getPlayer();
} catch (e) {
// Пользователь закрыл окно или отказался — это штатный сценарий,
// игра должна продолжаться в гостевом режиме.
}
}Если пропустить повторный вызов, player.isAuthorized() продолжит возвращать false, getName() останется пустым, а setScore() будет падать — и выглядеть это будет как «SDK не работает».
Готовый модуль игрока
Соберём всё в один файл, который подключается обычным тегом <script src="player.js"></script> — без сборки и без зависимостей.
// player.js — состояние игрока и авторизация.
const PlayerAPI = (() => {
let ysdk = null;
let player = null;
/** Вызывается один раз при старте игры, после YaGames.init(). */
async function init(sdk) {
ysdk = sdk;
try {
player = await ysdk.getPlayer();
} catch (e) {
// Объект Player недоступен — игра должна работать и так.
console.warn('getPlayer() недоступен:', e);
player = null;
}
return getProfile();
}
function isAuthorized() {
return Boolean(player && player.isAuthorized());
}
/** Данные для интерфейса. У гостя есть только id. */
function getProfile() {
if (!player) {
return { authorized: false, id: null, name: 'Игрок', photo: '' };
}
const authorized = player.isAuthorized();
return {
authorized,
id: player.getUniqueID(),
// Имя может быть пустым, даже если игрок вошёл:
// он мог запретить передачу персональных данных.
name: (authorized && player.getName()) || 'Игрок',
photo: authorized ? player.getPhoto('medium') : '',
};
}
/** Открывает окно Яндекс ID. Возвращает true, если игрок авторизован. */
async function signIn() {
if (!ysdk || isAuthorized()) return isAuthorized();
try {
await ysdk.auth.openAuthDialog();
} catch (e) {
return false; // Отказ пользователя — не ошибка.
}
try {
player = await ysdk.getPlayer();
} catch (e) {
console.warn('Не удалось перечитать Player:', e);
return false;
}
return isAuthorized();
}
return { init, signIn, isAuthorized, getProfile, getRaw: () => player };
})();Подключение кнопки. Обратите внимание на две детали, которые проверяет модерация: кнопка скрывается после успешного входа и не блокирует игру, пока идёт диалог.
const signInButton = document.getElementById('sign-in');
const benefitText = document.getElementById('sign-in-benefit');
function renderProfile(profile) {
document.getElementById('player-name').textContent = profile.name;
// После входа предложение авторизации больше не показываем (п. 1.2).
signInButton.hidden = profile.authorized;
benefitText.hidden = profile.authorized;
}
signInButton.addEventListener('click', async () => {
signInButton.disabled = true;
await PlayerAPI.signIn();
signInButton.disabled = false;
renderProfile(PlayerAPI.getProfile());
});
(async () => {
const ysdk = await YaGames.init();
renderProfile(await PlayerAPI.init(ysdk));
ysdk.features.LoadingAPI?.ready();
})();Разметка кнопки с объяснённой выгодой — то самое, чего требует 1.2.1:
<button id="sign-in" type="button">Войти через Яндекс ID</button>
<p id="sign-in-benefit">
Вход сохранит прогресс в облаке: играйте на телефоне и компьютере
с одного места и попадайте в таблицу рекордов.
</p>Данные профиля
| Метод | Возвращает | Доступен гостю |
|---|---|---|
player.getUniqueID() | Постоянный уникальный идентификатор, строка | Да |
player.isAuthorized() | true / false | Да |
player.getName() | Имя пользователя, строка | Нет |
player.getPhoto(size) | URL аватара; size — 'small', 'medium', 'large' | Нет |
player.getPayingStatus() | 'paying', 'partially_paying', 'not_paying', 'unknown' | Зависит от пользователя |
player.getIDsPerGame() | Promise<Array<{ appID, userID }>> — идентификаторы игрока в других ваших играх | Нет, требует авторизации |
getPhoto() и getName() синхронные и возвращают значения сразу — отдельного запроса не делают. getIDsPerGame() асинхронный, и перед вызовом документация советует проверить доступность: await ysdk.isAvailableMethod('player.getIDsPerGame').
Осторожно
Метод player.getID() устарел: он пока работает, но пишет предупреждение в консоль. Используйте getUniqueID(). Значения этих двух методов в общем случае не совпадают для одного и того же игрока, хотя у части пользователей могут оказаться одинаковыми. Если ваша игра уже привязывала данные к getID(), переносите их на getUniqueID(); для массовой миграции документация предлагает обратиться в поддержку. Метод player.getMode() тоже помечен устаревшим и будет удалён.
Гостевой прогресс и диалог выбора аккаунта
Требование 1.9 говорит прямо: прогресс сохраняется независимо от того, авторизован пользователь или нет. Технически ничего дополнительного делать не надо — player.setData() и player.setStats() работают у гостя так же.
Интереснее момент перехода. Игрок сначала играл гостем, потом вошёл в аккаунт — и у него оказалось два разных сохранения: одно под логином, другое без него. Платформа сама показывает диалог, где можно сравнить сохранения по длительности игры, дате последнего входа и другим параметрам и выбрать нужное. Ваша задача — не мешать этому и подхватить результат.
ysdk.on(ysdk.EVENTS.ACCOUNT_SELECTION_DIALOG_OPENED, () => {
// Останавливаем регулярную синхронизацию, пока игрок выбирает.
stopAutosave();
});
ysdk.on(ysdk.EVENTS.ACCOUNT_SELECTION_DIALOG_CLOSED, async () => {
// Выходим в главное меню и перечитываем игрока заново.
goToMainMenu();
const player = await ysdk.getPlayer();
const data = await player.getData();
applySave(data);
startAutosave();
});Если этого не сделать, игра продолжит работать со старым состоянием в памяти и первым же автосохранением затрёт выбор пользователя. Особенно важно для игр, которые часто синхронизируют данные или хранят прогресс на своём сервере.
player.signature: проверка игрока на сервере
Если у игры есть собственный бэкенд — с серверными сохранениями, античитом или начислением предметов, — нельзя доверять идентификатору, который прислал клиент: его подделает кто угодно. Для этого существует подпись.
const player = await ysdk.getPlayer({ signed: true });
await fetch('https://your.game.server/auth', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: player.signature,
});player.signature — это две base64-строки, разделённые точкой: <подпись>.<данные профиля>. Сервер разбирает их, считает HMAC-SHA256 от второй части с секретным ключом игры и сравнивает с первой. Совпало — данным можно верить.
Совет
Секретный ключ уникален для игры и формируется автоматически при создании инап-покупок; он показывается в Консоли разработчика на вкладке Инап-покупки → Настройки. То есть до подключения покупок проверить подпись просто нечем. Если сервера у вас нет, { signed: true } не нужен — обычного getPlayer() достаточно.
Не забудьте, что запросы к вашему серверу из игры — внешние сетевые запросы: домен придётся заранее указать на вкладке CSP в Консоли и обосновать.
Лимиты и типичные ошибки
ysdk.getPlayer() можно вызывать не чаще 20 раз за 5 минут — дальше запрос отклоняется с ошибкой. Звучит щедро, пока кто-то не вызовет getPlayer() внутри игрового цикла или на каждом открытии меню. Правило простое: получить объект один раз при старте, хранить в модуле и перечитывать только в двух случаях — после openAuthDialog() и после закрытия диалога выбора аккаунта.
Что ещё стоит проверить перед отправкой на модерацию:
- игра полностью проходима без входа в аккаунт, включая сохранение прогресса;
- окно входа не открывается автоматически при старте, после смерти персонажа или по таймеру;
- рядом с кнопкой входа написано, что он даёт;
- у пользователя есть возможность отказаться и продолжить игру;
- вошедшему игроку предложение авторизации больше не показывается;
- отказ от авторизации не роняет игру и не оставляет висеть экран загрузки;
- в игре нет ни одной формы регистрации и ни одной кнопки входа через сторонний сервис.
Отдельно про Claude Code: при генерации кода авторизации модель охотно предлагает { scopes: true } и getID() — это следы старых туториалов в обучающих данных. Если вы просите написать интеграцию, добавляйте в промпт актуальные сигнатуры или держите рядом файл с правилами проекта, чтобы не вычищать легаси вручную после каждой генерации.
Что дальше
Объект Player мы получили — теперь у него есть вторая половина: внутриигровые данные. В следующей главе разберём облачные сохранения: setData и getData для структурированного состояния, setStats и incrementStats для часто меняющихся чисел, параметр flush (именно параметр, а не метод) и лимиты, о которые чаще всего спотыкаются в продакшене.