Перейти к содержимому
Socialift
Безопасность

Как безопасно хранить и обновлять OAuth-токены соцсетей

Храните OAuth-токен только на сервере, в зашифрованном виде (Fernet или KMS) и отдельной строкой на каждого клиента и аккаунт. Следите за сроком по expires_at и обновляйте фоновым воркером до истечения. Платформы различаются: access token TikTok живёт 24 часа, Instagram Login и LinkedIn — 60 дней, токен страницы Facebook — бессрочно. Отзыв доступа видно по AuthError и вебхукам.

Обновлено:

Где и как хранить OAuth-токены?

Токен равен паролю. Не отдавайте его в браузер, мобильное приложение или логи: храните в зашифрованной колонке базы, а ключ шифрования — отдельно (переменная окружения, secret manager или KMS). Для каждого токена сохраняйте expires_at, фактически выданные scope и статус. Обновлением занимается планировщик, отзыв доступа выявляется через AuthError и вебхуки платформ.

SDK Socialift ничего не хранит: exchange_code() и refresh_token() возвращают TokenBundle (access_token, refresh_token, expires_in, expires_at), а куда это записать — решает ваше приложение. В разделе «Ulanishlar» (Подключения) кабинета Socialift токены шифруются на сервере и не отправляются в браузер — следуйте тому же правилу в своём бэкенде.

Сколько живёт access token: таблица по платформам

По официальной документации на 2026-09-17. Всегда считайте срок из expires_in в ответе — не зашивайте цифры из таблицы в код.

Сколько живёт access token: таблица по платформам — Таблица возможностей по платформам
Платформа / токенСрокОбновлениеSDK
Токен пользователя Facebook (краткосрочный)Несколько часовНа сервере обменивается на долгосрочный через fb_exchange_tokenfb.exchange_code(...) возвращает долгосрочный токен
Токен пользователя Facebook (долгосрочный)Около 60 днейИстёкшим токеном новый не получить — пользователь логинится зановоfb.granted_scopes(token)
Токен страницы Facebook (из долгосрочного токена пользователя)Без даты истеченияНе нужно; в определённых случаях аннулируетсяfb.pages(token)
Токен system user MetaБессрочный или 60 днейMeta рекомендует 60-дневный: создайте новый, разверните, отзовите старыйнет
Instagram LoginКраткосрочный — 1 час, долгосрочный — 60 днейОбновляется токен старше 24 часов и ещё не истёкший — ещё на 60 днейig.refresh_token(token)
TikTokAccess — 24 часа, refresh — 365 днейRefresh token в ответе может отличаться — используйте только новыйtt.refresh_token(refresh_token)
LinkedInAccess — 60 днейRefresh token только у одобренных партнёров MDP: 365 дней, обновление срок не продлеваетli.refresh(...), li.introspect(token)
Токен Telegram-ботаСрок в документации не указанВ BotFather можно в любой момент отозвать и получить новыйнет
Аккаунт Telegram (MTProto)Пока сеанс не завершён в списке активных сеансовПовторный вход (телефон → код → 2FA)Строка export_session() равна паролю
amoCRM OAuthAccess — в примере документации 24 часа; refresh — 3 месяцаRefresh token обменивается один разнет
Bitrix24 OAuthAccess — 1 час, refresh — 180 днейЧерез refresh_tokenнет

естьнетТаблицу можно прокрутить вбок

Примечания и ограничения

У Meta есть отдельный «срок доступа к данным»: если пользователь не активен 90 дней, данные не отдаются даже при валидном токене (некоторые бизнес-разрешения исключены). Статус показывает debug_token.

Стратегия refresh: когда и как обновлять токены

  1. Храните срок

    При каждом обновлении записывайте expires_at и (если есть) refresh_expires_at. Они уже есть в TokenBundle.

  2. Планируйте по окну

    Например, раз в час: TikTok — осталось меньше 4 часов, Instagram Login — меньше 10 дней (токен не моложе 24 часов), LinkedIn с refresh token — меньше 7 дней.

  3. Один токен — один обновляющий

    Ставьте блокировку на строку. В TikTok и amoCRM refresh token меняется: если два воркера обновят параллельно, второй получит ошибку со старым токеном.

  4. Пишите атомарно

    Новые access и refresh сохраняйте в одной транзакции. Если запись упала, новый refresh token потерян, и аккаунт отключится.

  5. Разделяйте ошибки

    AuthError — ставьте статус reauth_required и отправляйте пользователю ссылку на переподключение. RateLimitError или TransportError — повторите позже.

  6. Где нет refresh — предупреждайте заранее

    Для токена пользователя Facebook или LinkedIn без refresh напоминайте о переподключении за 7–14 дней до истечения.

Воркер обновления (Socialift SDK)

На провайдера достаточно одного клиента: токен передаётся аргументом в каждый метод и в клиенте не хранится.

refresh_worker.py
from socialift import ApiError, AuthError, Instagram, LinkedIn, RateLimitError, TikTok

async def refresh_row(row, tt: TikTok, ig: Instagram, li: LinkedIn) -> None:
    try:
        if row.provider == "tiktok":
            bundle = await tt.refresh_token(decrypt(row.refresh_token_enc))
        elif row.provider == "instagram":
            bundle = await ig.refresh_token(decrypt(row.access_token_enc))
        else:
            bundle = await li.refresh(decrypt(row.refresh_token_enc))
    except RateLimitError:
        raise  # повторить позже
    except ApiError as exc:
        if not isinstance(exc, AuthError) and exc.status != 400:
            raise
        await db.set_status(row.id, "reauth_required")
        return
    await db.save_bundle(  # одна транзакция
        row.id,
        access_token_enc=encrypt(bundle.access_token),
        refresh_token_enc=encrypt(bundle.refresh_token) if bundle.refresh_token else None,
        expires_at=bundle.expires_at,
    )

По стандарту OAuth отклонённый refresh token (invalid_grant) обычно возвращается с HTTP 400, а не 401 — поэтому такой случай тоже отправляем на переподключение.

Пусть db.save_bundle оставляет старый refresh token, если пришёл None: в ответе обновления Instagram refresh token нет. encrypt/decrypt — функции Fernet ниже.

Шифрование токенов: Fernet и ротация ключей

cryptography — собственная зависимость SDK. Fernet даёт аутентифицированное шифрование (AES-128-CBC + HMAC-SHA256), MultiFernet позволяет менять ключи.

token_crypto.py
import os
from cryptography.fernet import Fernet, MultiFernet

# TOKEN_KEYS="новый_ключ,старый_ключ" — шифруем первым
_keys = MultiFernet([Fernet(k) for k in os.environ["TOKEN_KEYS"].split(",")])

def encrypt(token: str) -> bytes:
    return _keys.encrypt(token.encode())

def decrypt(blob: bytes) -> str:
    return _keys.decrypt(blob).decode()

def reencrypt(blob: bytes) -> bytes:
    """Фоновая миграция после смены ключа: перешифровывает запись новым ключом."""
    return _keys.rotate(blob)

Ротация ключа: добавьте новый ключ в начало списка, прогоните все строки через reencrypt, затем уберите старый ключ. С KMS — envelope encryption: ключ данных шифруется в KMS, токены — ключом данных.

Как спроектировать таблицу токенов для multi-tenant?

Как спроектировать таблицу токенов для multi-tenant? — Таблица возможностей по платформам
КолонкаТипЗачем
tenant_iduuidФильтр в каждом запросе; токены клиентов не смешиваются (row-level security)
providertextfacebook, instagram, tiktok, linkedin, telegram_bot, telegram_account
account_idtextPage ID, IG user ID, TikTok open_id, LinkedIn sub; UNIQUE (tenant_id, provider, account_id)
token_kindtextuser, page, system_user, bot, session — от этого зависит правило обновления
access_token_enc, refresh_token_encbyteaТолько зашифрованные значения
key_versionintКаким ключом зашифровано — для ротации
expires_at, refresh_expires_attimestamptzПланировщик выбирает по индексу
scopestext[]Фактически выданные разрешения (не запрошенные)
statustextactive, reauth_required, revoked
last_refreshed_at, last_errortimestamptz, textДиагностика и алерты (без самого токена)

App secret, client secret и ключи шифрования в эту таблицу не пишутся — они живут в secret manager или переменных окружения.

Что такое appsecret_proof и зачем он нужен?

appsecret_proof — HMAC-SHA256 от access token с app secret в качестве ключа. Он добавляется к запросу Graph API параметром. Даже если токен украдут, без app secret proof не построить.

Если в App Dashboard включить «Require App Secret» (App Settings → Advanced → Security), вызовы без proof отклоняются — то есть клиентские запросы должны идти через бэкенд. SDK Socialift автоматически добавляет proof к запросам Meta (Facebook, Instagram, Ads); без SDK считайте его через socialift.crypto.appsecret_proof(access_token, app_secret) или hmac.

Как понять, что доступ отозван?

Meta: код 190 (истёкший или отозванный токен) приходит в SDK как AuthError. Подкоды объясняют причину: 458 — приложение удалено, 460 — сменён пароль, 463 — срок истёк, 467 — недействительный токен. Если пользователь удалил приложение на самом Facebook, вызывается deauthorize callback URL из App Dashboard; signed_request из него проверяет parse_signed_request(signed_request, app_secret).

TikTok при отзыве доступа шлёт вебхук authorization.removed (проверяйте через verify_tiktok_request). В LinkedIn li.introspect(token) возвращает статус токена (active, expired, revoked), а 401 превращается в AuthError. Если токен Telegram-бота отозван в BotFather, старый токен перестаёт работать — ответ Unauthorized SDK превращает в AuthError.

Когда пользователь жмёт «Отключить» в вашем приложении, сначала отзовите доступ у провайдера (fb.revoke(token), tt.revoke(token)), потом удалите строку. Одного удаления из базы мало: токен Meta продолжит действовать.

Минимальные разрешения и управление секретами

  • Запрашивайте только нужные функции scope — App Review и аудит проверяют то же самое.
  • Пользователь может отклонить часть разрешений — сохраняйте фактически выданные: fb.granted_scopes(token), в LinkedIn li.introspect(token).
  • Отдельные приложения и ключи для каждой среды (dev, staging, prod); продовые токены не должны попадать в dev-базу.
  • Заранее продумайте смену app secret: от него зависят appsecret_proof и проверка подписи вебхуков — обновляйте все сервисы одновременно.
  • Не пишите токены в логи, Sentry и аналитику; маскируйте их и в текстах ошибок.
  • Строку сеанса Telegram-аккаунта (export_session()) шифруйте так же, как токены.
  • Меняйте ключ шифрования минимум раз в год и при уходе сотрудника (MultiFernet или KMS).
  • Показывайте в кабинете аккаунты, которым нужно переподключение — тихий обрыв обходится дороже всего.

← Назад в блог

Частые вопросы

Можно ли хранить OAuth-токены в базе в открытом виде?

Не рекомендуется. Токен, утёкший через бэкап базы или SQL-инъекцию, используется сразу. Храните токен в зашифрованной колонке, а ключ — вне базы: тогда одной базы злоумышленнику не хватит.

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

Долгосрочный токен Instagram Login действует 60 дней и обновляется, если ему не меньше 24 часов и он ещё не истёк — ещё на 60 дней. На практике удобно обновлять фоновым воркером за 7–10 дней до истечения. Токен страницы, полученный через Facebook Login, обновлять не нужно.

Почему refresh token TikTok возвращает ошибку?

Чаще всего из-за повторного использования старого refresh token. По документации TikTok refresh token в ответе может отличаться, и использовать нужно новый. Сохраняйте его атомарно и не давайте двум воркерам обновлять один токен параллельно.

Почему LinkedIn не выдаёт refresh token?

Программные refresh token LinkedIn выдаёт только одобренным партнёрам Marketing Developer Platform. У остальных приложений access token живёт 60 дней, после чего пользователь проходит авторизацию заново — предупреждайте его заранее.

Истекает ли токен страницы Facebook?

У токена страницы, полученного из долгосрочного токена пользователя, нет даты истечения, но в определённых случаях он аннулируется — например, если пользователь сменил пароль или удалил приложение. Поэтому отслеживайте AuthError и храните статус.

Обязателен ли appsecret_proof?

Если в App Dashboard включён «Require App Secret» — да, вызовы без proof отклоняются. Даже без этой настройки добавлять proof к серверным запросам рекомендуется. SDK Socialift добавляет его к запросам Meta автоматически.

Источники

  1. Meta — Long-Lived Access Tokens
  2. Meta — Securing Graph API Requests
  3. Meta — Refresh Instagram Access Token
  4. TikTok — Manage User Access Tokens
  5. LinkedIn — Programmatic Refresh Tokens
  6. cryptography — Fernet

Связанные страницы