JWT-аутентификация для чата трансляций
JWT-аутентификация позволяет автоматически авторизовывать пользователей в чате трансляции и контролировать доступ на стороне вашего сервера. Это особенно полезно, если вы хотите интегрировать чат с вашей системой учёта пользователей.
Кому подходит эта статья
- Разработчикам платформ — нужно интегрировать чат трансляций с системой аутентификации
- Администраторам систем — требуется контроль доступа к чату трансляций
- Backend-разработчикам — нужно настроить безопасную авторизацию пользователей
Когда вам нужна JWT-аутентификация чата
Используйте JWT-аутентификацию, если:
- Нужен контроль доступа — вы хотите решать на своём сервере, кто может писать в чат
- Интеграция с вашей системой — нужно связать пользователей чата с вашей системой аутентификации
- Автоматическая авторизация — пользователи должны авторизовываться без дополнительных действий
- Привязка к системе учёта — нужно отслеживать, кто из ваших пользователей пишет в чат
Как работает JWT в чате (5 шагов)
JWT-аутентификация использует асимметричную криптографию (RSA) для безопасной передачи данных о пользователе:
- Вы создаёте пару ключей (приватный и публичный) на вашем сервере
- Публичный ключ сохраняется в Kinescope через API (приватный остаётся только у вас)
- Ваш сервер создаёт JWT-токен с данными пользователя и подписывает его приватным ключом
- Токен передаётся в URL чата как параметр
token - Kinescope проверяет подпись публичным ключом и авторизует пользователя
Как включить JWT-аутентификацию
Аутентификация настраивается индивидуально для каждой трансляции через API Kinescope. Передайте параметр chat_jwt_required: true для включения:
Важно: API-токен в заголовке
Authorization: Bearerдолжен быть в формате UUID. Получить токен можно в Dashboard Kinescope в разделе Настройки → API-токены. Подробнее об авторизации в общих правилах API.
curl --location --request PUT 'https://api.kinescope.io/v2/live/events/{{event_id}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer your_api_token_here' \
--data '{
"chat_jwt_required": true
}'
После включения пользователи будут автоматически авторизованы при переходе по ссылке с токеном:
https://kinescope.io/chat/{{event_id}}?token={{jwt}}
Теперь разберём, как настроить всё с нуля.
Настройка: шаг 1 — генерация ключей
Для JWT-аутентификации используется асимметричная криптография RSA, которая требует пару ключей:
- Приватный ключ — остаётся на вашем сервере, используется для подписи JWT-токенов
- Публичный ключ — передаётся в Kinescope через API, используется для проверки подписи токенов
Что такое JWK?
JWK (JSON Web Key) — это стандартизированный формат представления криптографических ключей (RFC 7517). Он позволяет безопасно обмениваться ключами между системами:
- Безопасность — приватный ключ никогда не покидает ваш сервер
- Ротация ключей — легко заменить ключи, просто загрузив новый публичный ключ
- Отзыв доступа — при компрометации ключа можно немедленно удалить публичный ключ из Kinescope
- Стандартизация — JWK поддерживается большинством библиотек и систем
Пример генерации JWK
Вот как выглядит генерация ключевой пары:
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"time"
"github.com/go-jose/go-jose/v3"
)
type JWK struct {
Kty string `json:"kty"` // тип ключа (RSA)
Kid string `json:"kid"` // идентификатор ключа (уникальный)
Use string `json:"use"` // назначение (sig для подписи)
Alg string `json:"alg"` // алгоритм (RS256)
N string `json:"n"` // модуль RSA ключа (base64url-encoded)
E string `json:"e"` // экспонента (обычно "AQAB")
}
// Генерация RSA ключевой пары размером 2048 бит
func generateRSAKeyPair() (*rsa.PrivateKey, *JWK, error) {
// Генерируем RSA ключевую пару
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
// Генерируем уникальный Key ID (kid)
kid := "key-" + time.Now().Format("2006-01-02")
// Формируем публичный ключ в формате JWK
publicKeyJWK := &JWK{
Kty: "RSA",
Kid: kid,
Use: "sig", // назначение - подпись
Alg: "RS256", // алгоритм - RSA с SHA-256
N: base64.RawURLEncoding.EncodeToString(privateKey.PublicKey.N.Bytes()),
E: base64.RawURLEncoding.EncodeToString([]byte{1, 0, 1}), // 65537 = AQAB
}
return privateKey, publicKeyJWK, nil
}
// Сохранение приватного ключа в PEM формате
func savePrivateKey(key *rsa.PrivateKey) ([]byte, error) {
return x509.MarshalPKCS8PrivateKey(key)
}
Пример готового публичного JWK в JSON:
{
"kty": "RSA",
"kid": "key-2024-12-25",
"use": "sig",
"alg": "RS256",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbwE...",
"e": "AQAB"
}
Что означают поля:
kty— тип ключа (RSA)kid— идентификатор ключа (уникальный в вашей системе)use— назначение ключа (sigдля подписи)alg— алгоритм подписи (RS256для RSA с SHA-256)n— модуль RSA ключа (base64url-encoded)e— экспонента RSA ключа (обычноAQAB, что означает 65537)
Сохранение публичного ключа в Kinescope
После генерации JWK сохраните публичную часть ключа в Kinescope через API. Обязательно передайте все параметры: kty, e, use, kid, alg, n и expires_at (дата истечения ключа в формате ISO 8601):
curl --location 'https://api.kinescope.io/v1/jwk' \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer your_api_token_here' \
--data '{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "key-2024-12-25",
"alg": "RS256",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbwE...",
"expires_at": "2026-12-31T23:59:59Z"
}'
Пример успешного ответа:
{
"id": "jwk_abc123def456",
"kty": "RSA",
"kid": "key-2024-12-25",
"created_at": "2024-12-25T10:00:00Z",
"expires_at": "2026-12-31T23:59:59Z"
}
Управление ключами
Просмотреть все активные ключи:
curl --location 'https://api.kinescope.io/v1/jwk' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer your_api_token_here'
Получить данные по конкретному ключу:
curl --location 'https://api.kinescope.io/v1/jwk/{{jwk_id}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer your_api_token_here'
Удалить ключ:
curl --location --request DELETE 'https://api.kinescope.io/v1/jwk/{{jwk_id}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer your_api_token_here'
Настройка: шаг 2 — генерация JWT-токена
Создайте JWT-токен (JSON Web Token, RFC 7519) с обязательными полями и подпишите его приватным ключом.
Обязательные поля
aud(audience) — значение"chat"(обязательно). Kinescope проверяет, что токен предназначен для чата.user_id— уникальный идентификатор пользователя в вашей системе. Используется для идентификации в чате.username— имя пользователя для отображения в чате. Будет показано рядом с сообщениями.event_id— ID трансляции (обязательно). Можно найти в личном кабинете или в ссылке на трансляцию.
Важно:
event_idнеобходим для эмбеда чата — чат может быть встроен на страницу без JWT-аутентификации, но при использовании JWT требуетсяevent_idдля привязки токена к конкретной трансляции.
Стандартные JWT-поля (рекомендуется)
exp(expiration) — время истечения токена (Unix timestamp). Рекомендуется устанавливать короткий срок жизни (например, 1 час).nbf(not before) — время, до которого токен недействителен (Unix timestamp). Полезно для токенов, которые должны стать активными в будущем.iat(issued at) — время создания токена (Unix timestamp). Помогает отслеживать возраст токена.
Все стандартные JWT-поля будут проверены системой Kinescope при валидации токена.
Пример генерации JWT
Вот как выглядит генерация и подпись JWT:
package main
import (
"crypto/rsa"
"time"
"github.com/golang-jwt/jwt/v5"
)
type ChatClaims struct {
UserID string `json:"user_id"`
Username string `json:"username"`
EventID string `json:"event_id"`
jwt.RegisteredClaims
}
// Генерация JWT токена
func generateJWT(privateKey *rsa.PrivateKey, kid string, userID, username, eventID string) (string, error) {
now := time.Now()
claims := ChatClaims{
UserID: userID,
Username: username,
EventID: eventID,
RegisteredClaims: jwt.RegisteredClaims{
Audience: []string{"chat"}, // обязательно "chat"
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(1 * time.Hour)), // по умолчанию 1 час
},
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
token.Header["kid"] = kid // Key ID
return token.SignedString(privateKey)
}
// Пример использования
func main() {
privateKey, publicKeyJWK, err := generateRSAKeyPair()
if err != nil {
panic(err)
}
kid := publicKeyJWK.Kid
// Генерируем токен
jwtToken, err := generateJWT(
privateKey,
kid,
"user-12345", // ID пользователя
"Иван Иванов", // Имя пользователя
"event-abc-123", // ID трансляции
)
if err != nil {
panic(err)
}
println("JWT токен:", jwtToken)
}
Для разработчиков: В реальной реализации используйте библиотеки для генерации JWT:
- Node.js:
node-jsonwebtoken(jwt.sign()),jose(new SignJWT().setProtectedHeader().sign())- Браузер: Web Crypto API (
crypto.subtle.sign())- Python:
PyJWT(jwt.encode())- Go:
github.com/golang-jwt/jwt/v5(jwt.NewWithClaims().SignedString())
Структура JWT-токена
JWT состоит из трёх частей, разделённых точками: header.payload.signature
Header (заголовок):
{
"alg": "RS256",
"typ": "JWT",
"kid": "key-2024-12-25"
}
Payload (данные):
{
"aud": "chat",
"user_id": "user-12345",
"username": "Иван Иванов",
"event_id": "event-abc-123",
"iat": 1703500800,
"exp": 1703504400
}
Signature (подпись): Подпись создаётся путём хеширования base64(header) + "." + base64(payload) с использованием приватного ключа по алгоритму RS256.
Готовый токен (пример):
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0yMDI0LTEyLTI1In0.eyJhdWQiOiJjaGF0IiwidXNlcl9pZCI6InVzZXItMTIzNDUiLCJ1c2VybmFtZSI6ItCY0LLQvdCwINCY0LLQvdCw0L3QvtCyIiwiZXZlbnRfaWQiOiJldmVudC1hYmMtMTIzIiwiaWF0IjoxNzAzNTAwODAwLCJleHAiOjE3MDM1MDQ0MDB9.signature_here
Использование токена
После генерации токена передайте его в URL чата:
https://kinescope.io/chat/event-abc-123?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0yMDI0LTEyLTI1In0...
Пример полного URL с токеном:
https://kinescope.io/chat/event-abc-123?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0yMDI0LTEyLTI1In0.eyJhdWQiOiJjaGF0IiwidXNlcl9pZCI6InVzZXItMTIzNDUiLCJ1c2VybmFtZSI6ItCY0LLQvdCwINCY0LLQvdCw0L3QvtCyIiwiZXZlbnRfaWQiOiJldmVudC1hYmMtMTIzIiwiaWF0IjoxNzAzNTAwODAwLCJleHAiOjE3MDM1MDQ0MDB9.signature_here
Готово! Теперь ваши пользователи смогут автоматически авторизовываться в чате трансляции через JWT-токены.
Безопасность
Ротация ключей
Рекомендуется регулярно обновлять ключи для повышения безопасности. Процесс ротации:
- Сгенерируйте новую пару ключей (приватный и публичный)
- Загрузите новый публичный ключ в Kinescope через API (старый ключ останется активным)
- Начните использовать новый приватный ключ для подписи новых токенов
- После периода перекрытия (когда все старые токены истекут) удалите старый публичный ключ из Kinescope
Действия при компрометации ключа
Если приватный ключ был скомпрометирован (утечка, подозрение на взлом):
- Немедленно удалите публичный ключ из Kinescope через API:
curl --location --request DELETE 'https://api.kinescope.io/v1/jwk/{{jwk_id}}' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer your_api_token_here' - Сгенерируйте новую пару ключей и загрузите новый публичный ключ
- Уведомите пользователей о необходимости повторной авторизации (если требуется)
- Проверьте логи на наличие подозрительной активности
Рекомендации по безопасности
- Размер ключа: Используйте ключи размером минимум 2048 бит (рекомендуется для RSA)
- Срок жизни токенов: Устанавливайте короткий срок жизни токенов (1-24 часа). Для долгоживущих сессий используйте механизм обновления токенов
- Срок жизни ключей: Устанавливайте
expires_atдля публичных ключей (например, 1-2 года) и планируйте ротацию до истечения - Хранение приватного ключа: Храните приватный ключ в безопасном хранилище (например, в секретах Kubernetes, AWS Secrets Manager, или зашифрованном хранилище)
- Мониторинг: Отслеживайте использование ключей и подозрительную активность
- Алгоритм: Используйте только RS256 (RSA с SHA-256). Другие алгоритмы не поддерживаются
Ограничения
- Поддерживаемые алгоритмы: Только RS256 (RSA с SHA-256)
- Минимальный размер ключа: 2048 бит
- Максимальное количество активных ключей: Ограничение может применяться на уровне аккаунта (уточните в поддержке)
Решение проблем
Токен не принимается системой
Проблема: Пользователь не может авторизоваться, токен отклоняется.
Возможные причины и решения:
Неверная подпись токена
- Убедитесь, что используете правильный приватный ключ для подписи
- Проверьте, что публичный ключ загружен в Kinescope и активен
- Убедитесь, что используете алгоритм RS256
Истёкший ключ
- Проверьте поле
expires_atпубличного ключа в Kinescope - Если ключ истёк, загрузите новый публичный ключ
- Проверьте поле
Неверный
event_id- Убедитесь, что
event_idв токене соответствует ID трансляции - Проверьте, что трансляция существует и JWT-аутентификация включена для неё
- Важно:
event_idобязателен для эмбеда чата — чат может быть встроен без JWT, но при использовании JWT требуется корректныйevent_id
- Убедитесь, что
Истёкший токен
- Проверьте поле
expв токене (время истечения) - Убедитесь, что системное время на сервере синхронизировано (NTP)
- Генерируйте новые токены с актуальным временем истечения
- Проверьте поле
Неверное поле
aud- Убедитесь, что поле
audимеет значение"chat"(строго в нижнем регистре)
- Убедитесь, что поле
Как проверить валидность токена
Вы можете проверить токен локально перед отправкой пользователю. Вот пример функции проверки:
package main
import (
"crypto/rsa"
"errors"
"github.com/golang-jwt/jwt/v5"
)
// Проверка валидности JWT токена
func verifyJWT(tokenString string, publicKey *rsa.PublicKey) (*ChatClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &ChatClaims{}, func(token *jwt.Token) (interface{}, error) {
// Проверяем алгоритм
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, errors.New("неподдерживаемый алгоритм")
}
return publicKey, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*ChatClaims); ok && token.Valid {
// Проверяем audience
if !claims.VerifyAudience("chat", true) {
return nil, errors.New("неверный audience")
}
return claims, nil
}
return nil, errors.New("неверный токен")
}
Для разработчиков: В реальной реализации используйте библиотеки для проверки JWT:
- Node.js:
node-jsonwebtoken(jwt.verify()),jose(jwtVerify())- Браузер: Web Crypto API (
crypto.subtle.verify())- Python:
PyJWT(jwt.decode())- Go:
github.com/golang-jwt/jwt/v5(jwt.ParseWithClaims())
Онлайн-инструменты:
- jwt.io — декодирование и проверка структуры токена (без проверки подписи)
- Проверьте структуру payload: должны присутствовать все обязательные поля
Частые ошибки при генерации ключей
Ошибка “invalid key format”
- Убедитесь, что используете правильный формат JWK (RFC 7517)
- Проверьте, что все обязательные поля присутствуют:
kty,e,n,kid,alg,use
Ошибка “key size too small”
- Используйте ключи размером минимум 2048 бит
- При генерации:
rsa.GenerateKey(rand.Reader, 2048)
Ошибка “key expired”
- Проверьте поле
expires_atпри загрузке ключа - Убедитесь, что дата истечения указана в формате ISO 8601:
"2026-12-31T23:59:59Z"
- Проверьте поле
Проблемы с эмбедом чата
Проблема: Чат не отображается или не авторизует пользователя при эмбеде.
Решения:
- Проверьте
event_idв токене — он должен соответствовать ID трансляции, к которой привязан чат - Убедитесь, что JWT-аутентификация включена для трансляции через API
- Проверьте формат URL — токен должен быть передан как параметр
token:
Пример:https://kinescope.io/chat/{{event_id}}?token={{jwt_token}}https://kinescope.io/chat/event-abc-123?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... - Проверьте консоль браузера на наличие ошибок JavaScript
Если проблема не решена, обратитесь в техническую поддержку с информацией:
- ID трансляции (
event_id) - Пример токена (можно замаскировать чувствительные данные)
- Описание проблемы и шаги воспроизведения
Всё! Теперь вы можете настроить JWT-аутентификацию для чата трансляций и автоматически авторизовывать пользователей.
Что дальше?
После настройки JWT-аутентификации рекомендуем:
- Руководство по проведению трансляций — настройка трансляций в Kinescope
- Общие правила API — авторизация и формат запросов
- Авторизационный бэкенд — контроль доступа к видео
Остались вопросы? Напишите в чат поддержки — специалисты помогут!