Skip to content

Instantly share code, notes, and snippets.

@MagistrAVSH
Last active January 6, 2026 13:40
Show Gist options
  • Select an option

  • Save MagistrAVSH/0b464663bf3819107156691f5c5f5685 to your computer and use it in GitHub Desktop.

Select an option

Save MagistrAVSH/0b464663bf3819107156691f5c5f5685 to your computer and use it in GitHub Desktop.

Руководство по интеграции аутентификации

Данный документ описывает процесс интеграции аутентификации на фронтенде (web/mobile) с бэкендом платформы.

Содержание

  1. Общая схема аутентификации
  2. Аутентификация агента (для SSO эндпоинтов)
  3. Шаг 1: Аутентификация через Privy SDK
  4. Шаг 2: Обмен Privy токена на JWT платформы
  5. Авторизация запросов к API
  6. Обновление токена
  7. Выход из системы
  8. Хранение токенов и cookies
  9. Коды ошибок
  10. Примеры кода

Общая схема аутентификации

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│    Frontend     │     │  Privy Service  │     │     Backend     │
└────────┬────────┘     └────────┬────────┘     └────────┬────────┘
         │                       │                       │
         │  1. Login (email/wallet/social)               │
         │──────────────────────>│                       │
         │                       │                       │
         │  2. Privy Access Token│                       │
         │<──────────────────────│                       │
         │                       │                       │
         │  3. POST /sso/exchange-privy-token            │
         │───────────────────────────────────────────────>│
         │   { privy_token, agent_id }                   │
         │                       │                       │
         │  4. { access_token, refresh_token, user }     │
         │<───────────────────────────────────────────────│
         │   + Set-Cookie: client_refresh_token          │
         │                       │                       │
         │  5. API запросы с JWT                         │
         │───────────────────────────────────────────────>│
         │   Authorization: Bearer <access_token>        │
         │   X-Auth-Type: jwt                            │
         │                       │                       │

Аутентификация агента (для SSO эндпоинтов)

Все /sso/* эндпоинты требуют идентификации агента. Существует два метода в зависимости от эндпоинта:

Метод 1: Basic Auth заголовок (для большинства SSO эндпоинтов)

Следующие эндпоинты поддерживают Basic Auth для идентификации агента:

  • POST /sso/refresh-token
  • POST /sso/logout
  • POST /sso/init-email-signin
  • POST /sso/confirm-email-signin
  • POST /sso/init-wallet-signin
  • POST /sso/confirm-wallet-signin

Формат заголовка

Authorization: Basic base64(agent_name:agent_secret)

Пример

Если ваши учётные данные агента:

  • Имя агента: my_mobile_app
  • Секрет агента: supersecret123

Закодируйте my_mobile_app:supersecret123 в Base64:

Authorization: Basic bXlfbW9iaWxlX2FwcDpzdXBlcnNlY3JldDEyMw==

Пример запроса

POST /sso/refresh-token HTTP/1.1
Host: api.example.com
Authorization: Basic bXlfbW9iaWxlX2FwcDpzdXBlcnNlY3JldDEyMw==
Content-Type: application/json

{
  "refresh_token": "abc123..."
}

Примечание: Если заголовок Authorization не предоставлен, бэкенд будет использовать агента по умолчанию (ID=1), который предназначен для основного веб-приложения платформы. Для сторонних интеграций необходимо предоставить учётные данные агента.

Метод 2: Agent ID в теле запроса (для exchange-privy-token)

Эндпоинт POST /sso/exchange-privy-token использует другой подход:

  • Агент идентифицируется через поле agent_id в теле запроса
  • Заголовок Origin проверяется на соответствие списку разрешённых origins агента
  • Заголовок Basic Auth не требуется

См. Шаг 2: Обмен Privy токена на JWT платформы для подробностей.

Получение учётных данных агента

Обратитесь к администратору бэкенда для получения:

  • agent_id - уникальный числовой идентификатор вашего приложения
  • agent_name - имя агента для Basic Auth
  • agent_secret - секрет агента для Basic Auth
  • Убедитесь, что домен вашего приложения добавлен в разрешённые origins агента

Autogames API Swagger

Swagger

Шаг 1: Аутентификация через Privy SDK

Первым шагом необходимо интегрировать Privy SDK в ваше приложение. Privy поддерживает множество методов аутентификации:

  • Email
  • SMS
  • Кошельки (MetaMask, Phantom и др.)
  • Социальные сети (Google, Twitter и др.)

После успешной аутентификации через Privy вы получите Privy Access Token.


Шаг 2: Обмен Privy токена на JWT платформы

Endpoint

POST /sso/exchange-privy-token

Request Body

{
  "privy_token": "string",   // Privy Access Token, полученный после аутентификации
  "agent_id": 1              // ID агента (приложения). Получите у администратора.
}

Response (200 OK)

{
  "user": {
    "ID": 123,
    "CreatedAt": "2024-01-15T10:00:00Z",
    "UpdatedAt": "2024-01-15T10:00:00Z",
    "DeletedAt": null,
    "Email": "user@example.com",
    "ImageURL": "https://...",
    "ImageThumbnailURL": "https://...",
    "Name": "User Name",
    "IsEmailVerified": true,
    "BalanceUSD": 100.50,
    "EthereumAddress": "0x...",
    "SolanaAddress": "..."
  },
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "abc123..."
}

Важно

  • Origin validation: Бэкенд проверяет заголовок Origin запроса. Убедитесь, что домен вашего приложения добавлен в список разрешённых для вашего agent_id.
  • Cookies: Помимо ответа в JSON, сервер также устанавливает HTTP-only cookie client_refresh_token с refresh токеном. Это запасной вариант для обновления токена.

Авторизация запросов к API

Для авторизованных запросов к API необходимо передавать два заголовка:

Заголовок Значение Описание
Authorization Bearer <access_token> JWT токен доступа
X-Auth-Type jwt Тип аутентификации

Пример запроса

GET /tokens HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
X-Auth-Type: jwt
Content-Type: application/json

Какие эндпоинты требуют авторизации?

  • Публичные эндпоинты (не требуют авторизации): получение списка токенов, игр, свечей, метрик
  • Защищённые эндпоинты (требуют авторизации): создание токенов, покупка/продажа, голосования, отправка сообщений в чат

Обновление токена

Когда access_token истекает, необходимо его обновить.

Endpoint

POST /sso/refresh-token

Заголовки

Заголовок Значение Обязателен
Authorization Basic base64(agent_name:agent_secret) Да (для сторонних приложений)
Content-Type application/json Да

Примечание: Если заголовок Authorization не предоставлен, используется агент по умолчанию (основная платформа).

Пример запроса

POST /sso/refresh-token HTTP/1.1
Host: api.example.com
Authorization: Basic bXlfbW9iaWxlX2FwcDpzdXBlcnNlY3JldDEyMw==
Content-Type: application/json

{
  "refresh_token": "abc123..."
}

Request Body

{
  "refresh_token": "abc123..."   // Опционально, если не передан - будет взят из cookie
}

Примечание: Refresh token можно передать либо в теле запроса, либо он будет автоматически извлечён из cookie client_refresh_token (если cookies отправляются с запросом).

Response (200 OK)

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "xyz789..."
}

Важно

  • При каждом обновлении выдаётся новый refresh_token. Используйте его для следующего обновления.
  • Cookie client_refresh_token также обновляется.

Когда обновлять?

Рекомендуется обновлять токен при получении ошибки:

  • 401 Unauthorized с кодом 2 (authorization failed)

Или проактивно за несколько минут до истечения (если вы декодируете JWT и проверяете exp claim).


Выход из системы

Endpoint

POST /sso/logout

Заголовки

Заголовок Значение Обязателен
Authorization Basic base64(agent_name:agent_secret) Да (для сторонних приложений)
Content-Type application/json Да

Примечание: Если заголовок Authorization не предоставлен, используется агент по умолчанию (основная платформа).

Пример запроса

POST /sso/logout HTTP/1.1
Host: api.example.com
Authorization: Basic bXlfbW9iaWxlX2FwcDpzdXBlcnNlY3JldDEyMw==
Content-Type: application/json

{
  "refresh_token": "abc123..."
}

Request Body

{
  "refresh_token": "abc123..."   // Опционально, если не передан - будет взят из cookie
}

Response (200 OK)

{
  "message": "Logged out successfully"
}

Что происходит при logout

  1. Refresh token инвалидируется на сервере
  2. Cookie client_refresh_token удаляется (MaxAge=-1)

Хранение токенов и cookies

Cookie: client_refresh_token

Бэкенд устанавливает HTTP-only cookie для хранения refresh token:

Параметр Значение Описание
Name client_refresh_token Имя cookie
HttpOnly true Недоступна из JavaScript (защита от XSS)
Secure true (prod) / false (local) Только HTTPS в production
SameSite None Разрешает cross-domain запросы
MaxAge 7 дней Время жизни cookie
Path / Доступна для всех путей

Рекомендации для фронтенда

Токен Где хранить Примечание
access_token В памяти (state/store) Короткий срок жизни, не сохранять в localStorage
refresh_token В secure storage / cookie Cookie устанавливается автоматически сервером

Для мобильного приложения

  • Access token: Храните в памяти приложения или в secure storage (Keychain на iOS, Keystore на Android)
  • Refresh token: Храните в secure storage. Передавайте в теле запроса при обновлении, т.к. работа с cookies может быть сложнее в mobile SDK.

Коды ошибок

Формат ошибки

{
  "Code": 2,
  "Error": "authorization failed"
}

Ошибки аутентификации

Code Error HTTP Status Описание Действие
2 authorization failed 401 Токен недействителен или отсутствует Обновить токен или повторить вход
18 privy token has expired 401 Privy токен истёк Переаутентифицироваться через Privy
19 privy token is invalid 401 Privy токен недействителен Переаутентифицироваться через Privy
6 refresh token not found 401 Refresh token не найден Повторить вход
7 refresh token is invalid 401 Refresh token недействителен Повторить вход
8 refresh token has expired 401 Refresh token истёк Повторить вход

Логика обработки ошибок

Получена ошибка 401?
├── Code = 2, 7, 8 → Попробовать refresh token
│   ├── Успех → Повторить оригинальный запрос
│   └── Ошибка → Редирект на логин
├── Code = 6 → Редирект на логин
└── Code = 18, 19 → Повторить аутентификацию через Privy

Примеры кода

React Native / Mobile (TypeScript)

// auth-service.ts
import * as SecureStore from 'expo-secure-store';

const API_BASE_URL = 'https://api.example.com';
const AGENT_ID = 1; // Ваш agent_id

// Учётные данные агента для Basic Auth (получите у администратора бэкенда)
const AGENT_NAME = 'my_mobile_app';
const AGENT_SECRET = 'supersecret123';

// Генерация заголовка Basic Auth
function getBasicAuthHeader(): string {
  const credentials = `${AGENT_NAME}:${AGENT_SECRET}`;
  const encoded = btoa(credentials); // или используйте библиотеку base-64 для React Native
  return `Basic ${encoded}`;
}

interface AuthTokens {
  accessToken: string;
  refreshToken: string;
}

interface User {
  ID: number;
  Email: string;
  Name: string;
  // ... другие поля
}

interface ExchangeTokenResponse {
  user: User;
  access_token: string;
  refresh_token: string;
}

// Хранение токенов
let accessToken: string | null = null;

async function saveRefreshToken(token: string): Promise<void> {
  await SecureStore.setItemAsync('refresh_token', token);
}

async function getRefreshToken(): Promise<string | null> {
  return await SecureStore.getItemAsync('refresh_token');
}

async function clearTokens(): Promise<void> {
  accessToken = null;
  await SecureStore.deleteItemAsync('refresh_token');
}

// Обмен Privy токена
export async function exchangePrivyToken(privyToken: string): Promise<{ user: User; tokens: AuthTokens }> {
  const response = await fetch(`${API_BASE_URL}/sso/exchange-privy-token`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      privy_token: privyToken,
      agent_id: AGENT_ID,
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.Error || 'Token exchange failed');
  }

  const data: ExchangeTokenResponse = await response.json();
  
  // Сохраняем токены
  accessToken = data.access_token;
  await saveRefreshToken(data.refresh_token);

  return {
    user: data.user,
    tokens: {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
    },
  };
}

// Обновление токена
export async function refreshAccessToken(): Promise<AuthTokens> {
  const refreshToken = await getRefreshToken();
  
  if (!refreshToken) {
    throw new Error('No refresh token available');
  }

  const response = await fetch(`${API_BASE_URL}/sso/refresh-token`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': getBasicAuthHeader(), // Аутентификация агента
    },
    body: JSON.stringify({
      refresh_token: refreshToken,
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    // Если refresh token недействителен - очищаем всё
    if ([6, 7, 8].includes(error.Code)) {
      await clearTokens();
    }
    throw new Error(error.Error || 'Token refresh failed');
  }

  const data = await response.json();
  
  accessToken = data.access_token;
  await saveRefreshToken(data.refresh_token);

  return {
    accessToken: data.access_token,
    refreshToken: data.refresh_token,
  };
}

// Выход из системы
export async function logout(): Promise<void> {
  const refreshToken = await getRefreshToken();
  
  if (refreshToken) {
    try {
      await fetch(`${API_BASE_URL}/sso/logout`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': getBasicAuthHeader(), // Аутентификация агента
        },
        body: JSON.stringify({
          refresh_token: refreshToken,
        }),
      });
    } catch {
      // Игнорируем ошибки при logout
    }
  }

  await clearTokens();
}

// Авторизованный запрос с автоматическим обновлением токена
export async function authorizedFetch(
  url: string, 
  options: RequestInit = {}
): Promise<Response> {
  const makeRequest = async (token: string) => {
    const headers = {
      ...options.headers,
      'Authorization': `Bearer ${token}`,
      'X-Auth-Type': 'jwt',
      'Content-Type': 'application/json',
    };

    return fetch(`${API_BASE_URL}${url}`, {
      ...options,
      headers,
    });
  };

  if (!accessToken) {
    throw new Error('Not authenticated');
  }

  let response = await makeRequest(accessToken);

  // Если 401 - пробуем обновить токен
  if (response.status === 401) {
    try {
      const tokens = await refreshAccessToken();
      response = await makeRequest(tokens.accessToken);
    } catch {
      // Refresh не удался - нужен повторный вход
      throw new Error('Session expired');
    }
  }

  return response;
}

Использование

import { usePrivy } from '@privy-io/react-auth';
import { exchangePrivyToken, authorizedFetch, logout } from './auth-service';

// После успешной аутентификации через Privy
async function handlePrivyAuth() {
  const { getAccessToken } = usePrivy();
  
  // Получаем Privy токен
  const privyToken = await getAccessToken();
  
  // Обмениваем на токены платформы
  const { user, tokens } = await exchangePrivyToken(privyToken);
  
  console.log('Authenticated user:', user);
}

// Пример API запроса
async function createToken(tokenData: any) {
  const response = await authorizedFetch('/tokens', {
    method: 'POST',
    body: JSON.stringify(tokenData),
  });
  
  if (!response.ok) {
    throw new Error('Failed to create token');
  }
  
  return response.json();
}

// Выход
async function handleLogout() {
  await logout();
  // Перенаправить на экран входа
}

Checklist для интеграции

  • Интегрировать Privy SDK
  • Получить учётные данные агента от администратора бэкенда:
    • agent_id - для эндпоинта обмена токенов
    • agent_name и agent_secret - для Basic Auth на других SSO эндпоинтах
  • Убедиться, что домен приложения добавлен в allowed origins для агента
  • Реализовать обмен Privy токена на JWT платформы
  • Настроить хранение токенов (secure storage для mobile)
  • Добавить заголовок Authorization: Basic ... к запросам /sso/refresh-token и /sso/logout
  • Реализовать автоматическое обновление токена при 401 ошибке
  • Реализовать функцию logout
  • Добавить заголовки Authorization: Bearer ... и X-Auth-Type: jwt ко всем защищённым API запросам

FAQ

Q: Сколько живёт access_token?
A: Срок жизни access token определяется настройками бэкенда. Рекомендуется обрабатывать 401 ошибку и обновлять токен по необходимости.

Q: Сколько живёт refresh_token?
A: 7 дней. После истечения пользователю нужно повторно войти через Privy.

Q: Что делать если и access и refresh токены истекли?
A: Перенаправить пользователя на экран входа и начать процесс аутентификации через Privy заново.

Q: Нужно ли передавать cookies в мобильном приложении?
A: Нет, для мобильных приложений рекомендуется передавать refresh_token в теле запроса, а не полагаться на cookies.

Q: Где получить agent_id?
A: Обратитесь к администратору бэкенда. Каждому приложению/домену выдаётся уникальный agent_id.

Q: В чём разница между Basic Auth и Bearer Auth?
A:

  • Basic Auth (Authorization: Basic ...) используется для идентификации агента на SSO эндпоинтах (/sso/refresh-token, /sso/logout). Он идентифицирует ваше приложение/клиент.
  • Bearer Auth (Authorization: Bearer ...) используется для аутентификации пользователя на защищённых API эндпоинтах. Он идентифицирует авторизованного пользователя.

Q: Нужны ли мне и agent_name/agent_secret, И agent_id?
A: Да. agent_id используется в теле запроса /sso/exchange-privy-token, тогда как agent_name и agent_secret используются для Basic Auth на других SSO эндпоинтах (refresh и logout).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment