Другое

Как исправить проблемы аутентификации JWT в SignalR WebSocket

Узнайте, как устранить сбои соединения SignalR WebSocket при использовании аутентификации JWT: настройка CORS, передача токенов в строке запроса и логика повторного подключения, включая проверку параметров валидации токенов и включение sticky‑sessions для масштабируемых серверных конфигураций.

Как устранить сбой подключения SignalR WebSocket с использованием JWT‑аутентификации?

При попытке подключиться к SignalR‑хабу с JWT‑аутентификацией я получаю ошибку WebSocket:

[2025-11-19T14:03:33.645Z] Error: Failed to start the transport 'WebSockets': Error: WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.

Конфигурация клиента выглядит так:

javascript
this.hubConnection = new signalR.HubConnectionBuilder()
  .withUrl(
    `${environment.serviceUrl}/hubs/notifications?userId=${userId}&applicationId=${applicationId}`,
    {
      accessTokenFactory: () => {
        return this.authService.idToken;
      },
      transport: signalR.HttpTransportType.WebSockets
    }
  )
  .withAutomaticReconnect()
  .configureLogging(signalR.LogLevel.Debug)
  .build();

this.hubConnection
  .start()
  .then(() => console.log('[SignalR] Connected'))
  .catch(err => console.error('[SignalR] Connection error:', err));

На сервере уже настроен CORS:

csharp
services.AddCors(o => o.AddPolicy("AllowCors", builder =>
{
    builder.SetIsOriginAllowed(origin => true)
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials();
}));

Подключение работает без аутентификации. Что может вызывать эту проблему и как её решить?

SignalR WebSocket‑соединения с аутентификацией JWT обычно падают из‑за некорректной серверной конфигурации JWT, которая не обрабатывает токены из строки запроса для WebSocket‑соединений, проблем с CORS при работе с учетными данными или отсутствия логики повторного подключения. Основное решение состоит в настройке JWT‑а так, чтобы он считывал токены из строки запроса во время WebSocket‑соединений, и в корректной настройке CORS, поддерживающей учетные данные.

Содержание

Понимание проблемы

Сообщение об ошибке указывает, что WebSocket‑соединение падает во время процесса аутентификации. Ключевой момент — SignalR с WebSocket использует иной поток аутентификации по сравнению с обычными HTTP‑запросами. При использовании WebSocket SignalR автоматически помещает JWT‑токен в строку запроса (?access_token=YOUR_TOKEN), а не в заголовки, потому что браузерный API WebSocket не поддерживает пользовательские заголовки.

Это приводит к несовпадению: серверная часть JWT‑а может быть настроена на поиск токенов в заголовках (для обычных HTTP‑запросов), но не в строке запроса (для WebSocket‑соединений). Соединение работает без аутентификации, потому что middleware аутентификации не вмешивается в установку соединения.

Серверная конфигурация JWT

Самый критичный исправление — настроить JWT‑а так, чтобы он обрабатывал токены из строки запроса для WebSocket‑соединений. Согласно документации Microsoft по аутентификации SignalR, нужно подключить событие OnMessageReceived:

csharp
services.AddAuthentication(x => 
{
    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x => 
{
    x.RequireHttpsMetadata = false;
    x.SaveToken = true;
    x.TokenValidationParameters = new TokenValidationParameters
    {
        // Ваши существующие параметры проверки
    };
    
    // Это ключевая часть для SignalR с WebSockets
    x.Events = new JwtBearerEvents
    {
        OnMessageReceived = context =>
        {
            var accessToken = context.Request.Query["access_token"];
            
            // Если запрос относится к нашему хабу...
            if (!string.IsNullOrEmpty(accessToken))
            {
                // Читаем токен из строки запроса
                context.Token = accessToken;
            }
            
            return Task.CompletedTask;
        }
    };
});

Эта конфигурация гарантирует, что когда SignalR отправляет JWT‑токен в строке запроса во время WebSocket‑соединения, middleware аутентификации сможет корректно прочитать и проверить его.

Проблемы с CORS

Текущая конфигурация CORS имеет критическую ошибку: SetIsOriginAllowed(origin => true) может вызвать проблемы с учетными данными. Согласно исследованиям из документации Microsoft, приложения должны быть настроены так, чтобы проверять эти заголовки и гарантировать, что только WebSocket‑соединения из ожидаемых источников разрешены.

Обновите конфигурацию CORS, чтобы использовать конкретные источники вместо подстановочных знаков:

csharp
services.AddCors(o => o.AddPolicy("AllowCors", builder =>
{
    // Замените на ваш реальный фронтенд‑домен
    builder.WithOrigins("https://your-frontend-domain.com")
           .AllowAnyMethod()
           .AllowAnyHeader()
           .AllowCredentials();
}));

Исследование из GitHub issue #4457 конкретно упоминает, что «чтобы не получать ошибку CORS, вместо возвращения wildcard ‘*’ сервер должен возвращать разрешённый origin как URL, поскольку это не работает с учетными данными, которые нужны для SignalR».


Важное замечание по безопасности

Обратите внимание, что передача JWT‑токенов в строке запроса имеет последствия для безопасности. Токены могут быть залогированы серверами, прокси и браузерами. Рассмотрите возможность использования токенов с коротким сроком действия и механизма обновления токенов.

Проверка клиента

Конфигурация клиента выглядит в целом корректной, но есть несколько оптимизаций:

1. Добавьте логику повторного подключения при ошибках 401

Согласно руководству по устранению неполадок Microsoft, стоит добавить логику повторного подключения для обработки ошибок 401:

javascript
this.hubConnection.onclose(error => {
    if (error && error.message.includes("401")) {
        console.log('[SignalR] 401 error detected, attempting to reconnect...');
        // Возможно, захотите обновить токен здесь
        this.authService.refreshToken().then(() => {
            // Повторить подключение с новым токеном
            this.hubConnection.start();
        });
    }
});

2. Рассмотрите возможность пропуска этапа переговоров

В некоторых случаях можно пропустить фазу переговоров, чтобы упростить соединение:

javascript
this.hubConnection = new signalR.HubConnectionBuilder()
  .withUrl(
    `${environment.serviceUrl}/hubs/notifications?userId=${userId}&applicationId=${applicationId}`,
    {
      accessTokenFactory: () => this.authService.idToken,
      transport: signalR.HttpTransportType.WebSockets,
      skipNegotiation: true  // Добавьте эту опцию
    }
  )
  .withAutomaticReconnect()
  .configureLogging(signalR.LogLevel.Debug)
  .build();

Однако это требует, чтобы ваш сервер поддерживал WebSocket напрямую без переговоров.

Расширенные решения

1. Конфигурация нескольких серверов

Если вы используете несколько серверов, убедитесь, что sticky sessions включены, как указано в сообщении об ошибке. Это можно настроить в настройках балансировщика нагрузки:

csharp
// В конфигурации стартапа
services.AddSignalR()
    .AddAzureSignalR(options => 
    {
        // Конфигурация Azure SignalR
        options.ServerStickyMode = Microsoft.Azure.SignalR.ServerStickyMode.Required;
    });

2. Проблемы с настройкой Authority

Исследования из Stack Overflow показывают, что иногда настройки Authority JWT могут вызывать проблемы. Если у вас есть x.Authority = "Authority URL" в конфигурации JWT, попробуйте отключить его или убедиться, что URL правильный.

3. Параметры проверки токена

Проверьте параметры проверки токена, чтобы убедиться, что они соответствуют издателю токена:

csharp
x.TokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = new SymmetricSecurityKey(key),
    ValidateIssuer = true,  // Должен совпадать с издателем токена
    ValidateAudience = true, // Должен совпадать с аудиторией токена
    ValidIssuer = "your-issuer",
    ValidAudience = "your-audience",
    ClockSkew = TimeSpan.Zero
};

Частые сценарии ошибок

1. 401 Unauthorized во время рукопожатия WebSocket

Симптом: Соединение работает без аутентификации, но падает с JWT
Решение: Убедитесь, что событие OnMessageReceived корректно настроено для чтения из строки запроса

2. Политика CORS блокирует соединение

Симптом: Соединение падает с ошибками, связанными с CORS
Решение: Используйте конкретные источники вместо wildcard и убедитесь, что AllowCredentials() включен

3. Токен не отправляется в строке запроса

Симптом: Аутентификация падает, но токен не виден в URL соединения
Решение: Проверьте, что accessTokenFactory возвращает валидный токен, и посмотрите вкладку Network в браузере

4. Проблемы с несколькими серверами

Симптом: Перебрасывающиеся ошибки соединения
Решение: Включите sticky sessions в вашем балансировщике нагрузки


Заключение

Проблемы с WebSocket‑соединениями SignalR и аутентификацией JWT обычно вызваны настройками сервера, а не клиентом. Ключевые шаги по решению:

  1. Настройте JWT‑а на чтение токенов из строки запроса через обработчик OnMessageReceived.
  2. Исправьте конфигурацию CORS, используя конкретные источники вместо wildcard при работе с учетными данными.
  3. Добавьте логику повторного подключения для автоматического обновления токенов и обработки ошибок 401.
  4. Проверьте параметры проверки токена, чтобы они соответствовали издателю и аудитории.
  5. Рассмотрите sticky sessions, если используете несколько серверов за балансировщиком нагрузки.

Пошагово устраняя эти области, вы сможете установить безопасные WebSocket‑соединения с JWT‑аутентификацией в SignalR. Если проблемы сохраняются, проверьте сетевые запросы в браузере, чтобы точно определить, где происходит сбой аутентификации, и просмотрите журналы сервера для подробной информации об ошибках.

Источники

  1. Authentication and authorization in ASP.NET Core SignalR - Microsoft Learn
  2. Security considerations in ASP.NET Core SignalR - Microsoft Learn
  3. Troubleshooting guide for Azure SignalR Service - Microsoft Learn
  4. JWT Authentication Failed with SignalR - Stack Overflow
  5. SignalR JWT authentication fails - Stack Overflow
  6. ASP.NET Core SignalR Authentication and Authorization 401 - GitHub Issue
  7. Credential CORS issue - GitHub Issue
  8. Securing Angular SignalR client with JWT - Damien Bod
Авторы
Проверено модерацией
Модерация