corssignalrasp.net-core-6.0

SignalR - .NET Core 6.0 working in local and failing with cors error once deployed


I am implementing simple signal R real-time messaging. It works absolutely fine in local but moment its deployed i get CORS error

8DUpdate.html:1 Access to fetch at 'https://test.test.test.com/chatHub/negotiate?negotiateVersion=1' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.Understand this errorAI signalr.min.js:1

Preflight request :

Request URL: https://test.test.test.com/chatHub/negotiate?negotiateVersion=1 Request Method: OPTIONS Status Code: 405 Not Allowed Remote Address: 10.11.111.4:443 Referrer Policy: strict-origin-when-cross-origin

I tried allowing specific origins, all origins and every possible CORS related options i found. I doubt i am messing up with the order somewhere?

Below is Program.cs code

using System.IdentityModel.Tokens.Jwt;
using DotNetSmartLogger;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.OpenApi.Models;
using NLog;
using NLog.Web;
using Quality.EightD.API;
using Quality.EightD.API.Behaviour;
using Quality.EightD.API.ChatHub;

var builder = WebApplication.CreateBuilder(args);

// QA Appsetting testing
//builder.Configuration
//    .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true);

// Add services to the container.

builder
    .Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
    });
builder
    .Services.AddSignalR(hubOptions =>
    {
        hubOptions.EnableDetailedErrors = true;
        hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(15); // Reduce from 1 minute
        hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(30); // Reduce from 2 minutes
        hubOptions.HandshakeTimeout = TimeSpan.FromSeconds(15); // Add handshake timeout
        hubOptions.MaximumReceiveMessageSize = 102400; // Increased to 100KB
        hubOptions.StreamBufferCapacity = 10;
    })
    .AddJsonProtocol(options =>
    {
        options.PayloadSerializerOptions.PropertyNamingPolicy = null;
    });

builder.Services.AddCors(options =>
{
    options.AddPolicy(
        "CORSPolicy",
        builder =>
        {
            builder
                .WithOrigins("http://localhost:8080", "https://test.test.test.com")
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials();
        }
    );
});

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.ConfigureRepositories();
builder.Services.AddSwaggerGen(setup =>
{
    // Include 'SecurityScheme' to use JWT Authentication
    var jwtSecurityScheme = new OpenApiSecurityScheme
    {
        Scheme = "bearer",
        BearerFormat = "JWT",
        Name = "JWT Authentication",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.Http,
        Description = "Put **_ONLY_** your JWT Bearer token on textbox below!",
        Reference = new OpenApiReference
        {
            Id = JwtBearerDefaults.AuthenticationScheme,
            Type = ReferenceType.SecurityScheme,
        },
    };
    setup.SwaggerDoc("v1", new OpenApiInfo { Title = "Quality.EightD.API", Version = "v1" });
    setup.AddSecurityDefinition(jwtSecurityScheme.Reference.Id, jwtSecurityScheme);
    setup.AddSecurityRequirement(
        new OpenApiSecurityRequirement { { jwtSecurityScheme, Array.Empty<string>() } }
    );
});

// Configure smart logger
builder.Services.AddLogging(loggingBuilder =>
{
    loggingBuilder.ClearProviders();
    loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
    loggingBuilder.AddNLogWeb();
});

// Configure Kestrel server options
builder.Services.Configure<KestrelServerOptions>(options =>
{
    options.Limits.MaxRequestBodySize = long.MaxValue; // if don't set default value is: 30 MB
});

// Configure form options
builder.Services.Configure<FormOptions>(o =>
{
    o.ValueLengthLimit = int.MaxValue;
    o.MultipartBodyLengthLimit = int.MaxValue;
    o.MultipartBoundaryLengthLimit = int.MaxValue;
    o.MultipartHeadersCountLimit = int.MaxValue;
    o.MultipartHeadersLengthLimit = int.MaxValue;
    o.BufferBodyLengthLimit = int.MaxValue;
    o.BufferBody = true;
    o.ValueCountLimit = int.MaxValue;
});

builder.Services.AddHttpContextAccessor();

// Configure JWT Authentication
builder
    .Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                if (
                    (
                        !string.IsNullOrEmpty(accessToken)
                        || context.Request.Headers.ContainsKey("Authorization")
                    ) && path.StartsWithSegments("/chatHub")
                )
                {
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            },
        };
        options.RequireHttpsMetadata = false; // For development - set to true in production
        options.SaveToken = true;
    });

var app = builder.Build();

// Configure Swagger
app.UseSwagger(c =>
{
    c.RouteTemplate = "eightd-api-svc/{documentName}/swagger.json";
});

app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/eightd-api-svc/v1/swagger.json", "API Service V1");
    c.RoutePrefix = "eightd-api-svc";
    c.ConfigObject.AdditionalItems["syntaxHighlight"] = new Dictionary<string, object>
    {
        ["activated"] = false,
    };
});

LogManager.Configuration.Variables["logPath"] = builder.Configuration.GetValue<string>("logPath");

// Update the order of middleware
// First, essential middleware
app.UseRouting();
app.UseCors("CORSPolicy");
app.UseAuthentication();
app.UseAuthorization();

// Add buffering middleware early in the pipeline
app.Use(
    async (context, next) =>
    {
        // Enable buffering for all requests
        context.Request.EnableBuffering();
        await next();
    }
);

// Then your custom middleware
app.UseMiddleware<JwtAuthenticationBehaviour>();
app.UseMiddleware<ResponseMiddleware>();
app.UseMiddleware<NLogRequestPostedBodyMiddleware>();
app.UseMiddleware<SmartLoggingMiddleware>();
app.UseMiddleware<ExceptionHandlingMiddleware>();

// Finally endpoints
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapHub<ChatHub>("/chatHub");
});

app.UseHttpsRedirection();
app.MapControllers();

app.Run();

Solution

  • Issue :

    // Finally endpoints
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapHub<ChatHub>("/chatHub");
    });
    

    Fix :

    // Finally endpoints
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapHub<ChatHub>("eightd-api-svc/chatHub");
    });