0

My statup.cs class code looks like this:

namespace Tenet.eReserve.Api.WebApi
{
    public class Startup
    {
        public IConfiguration Configuration { get; }
        public string SuperSecret { get; set;  }
        public Startup(IConfiguration configuration) => Configuration = configuration;


        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions();
            services.AddSingleton<IConfiguration>(Configuration);
            services.Configure<Settings>(Configuration.GetSection("eReserve"));

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "eReserve API List", Version = "v1" });
                c.AddSecurityDefinition("Bearer",
                    new OpenApiSecurityScheme
                    {
                        Description = "JWT Authorization header using the Bearer scheme.",
                        Type = SecuritySchemeType.Http, //We set the scheme type to http since we're using bearer authentication
                        Scheme = "bearer"
                    });

                c.AddSecurityRequirement(new OpenApiSecurityRequirement{
                    {
                        new OpenApiSecurityScheme{
                            Reference = new OpenApiReference{
                                Id = "Bearer", //The name of the previously defined security scheme.
                                Type = ReferenceType.SecurityScheme
                            }
                        },new List<string>()
                    }
                });

            });

            services.AddControllers().AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
            }).AddNewtonsoftJson(); // to convert string values to int32 and decimal   

            services.AddScoped<IReportService, ReportService>();
            services.AddScoped<ISearchService, SearchService>();


            var sp = services.BuildServiceProvider();

            // This will succeed.
            var settings = sp.GetService<IOptionsSnapshot<Settings>>();

            services.AddApplicationInsightsTelemetry(options =>
            {
                options.EnableDebugLogger = false;
            });
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder.WithOrigins(settings.Value.SignalROrigniURL) //http://localhost:8090")
                               .AllowAnyHeader()
                           .AllowAnyMethod()
                           .SetIsOriginAllowed((x) => true)
                           .AllowCredentials());

                //  options.AddPolicy("CorsPolicy",
                //                builder => builder.AllowAnyOrigin()
                //.AllowAnyMethod()
                //.AllowAnyHeader());
            });
            services.AddAuthentication(opt =>
            {
                opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(
            );
            services.ConfigureOptions<ConfigureJwtBearerOptions>();
            services.AddHttpContextAccessor();
            services.AddAuthorization(options =>
            {
                options.AddPolicy("HospitalCode", policy =>
                    policy.Requirements.Add(new HospitalCodeRequirement()));
            });
            services.AddSingleton<IAuthorizationHandler, FacilityHandler>();
            services.AddSignalR(options =>
            {
                options.EnableDetailedErrors = true;
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, TelemetryConfiguration configuration, IOptionsSnapshot<Settings> settings)
        {
   
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseAzureAppConfiguration();
            app.UseSwagger();
            // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
            // specifying the Swagger JSON endpoint.
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "eReserve API V1");
                c.RoutePrefix = String.Empty;
            });
            configuration.InstrumentationKey = settings.Value.AppInsightsInstrumentationKey;
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseCors("CorsPolicy");
            app.UseAuthentication(); // this one first
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
           {
               endpoints.MapControllers();
               endpoints.MapHub<NotifyClientService>("/lockNotification");
           });
            //app.UseSignalR(routes =>
            //{
            //    routes.MapHub<NotifyClientService>("/lockNotification");
            //});
        }
    }
}

And in the angular I am creating a connection like this:

private hubConnection: signalR.HubConnection;

public startConnection = () => {
  this.hubConnection = new signalR.HubConnectionBuilder()
    .withUrl(this.apiBase + 'lockNotification')
    .build();
  this.hubConnection
    .start()
    .then(() => console.log('Connection started'))
    .catch(err => console.log('Error while starting connection: ' + err));
}

public startListener = () => {
  this.hubConnection.on('LockNotification', (facilityName, lockType, hospitalCode) => {
   //logic

    }
  });
}

This arrangement works locally but when deployed it give CORS error:

Access to fetch at 'https://abc-api.com/lockNotification/negotiate?negotiateVersion=1' from origin 'https://abc-ui.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. zone.js:1118 POST https://xxx-xxx.com/lockNotification/negotiate?negotiateVersion=1 net::ERR_FAILED

Please note that "settings.Value.SignalROrigniURL" return "https://abc-ui.com" (fake url)

UPDATE

In the ConfigureServices method i have Cors like this:

services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy",
                  builder => builder.AllowAnyOrigin()
  .AllowAnyMethod()
  .AllowAnyHeader());
});

Not sure if above is required and in Configure I followed the documentation.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, TelemetryConfiguration configuration, IOptionsSnapshot<Settings> settings)
{
   
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseAzureAppConfiguration();
    app.UseSwagger();
    // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
    // specifying the Swagger JSON endpoint.
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "eReserve API V1");
        c.RoutePrefix = String.Empty;
    });
    configuration.InstrumentationKey = settings.Value.AppInsightsInstrumentationKey;
    app.UseHttpsRedirection();
    app.UseCors(builder =>
    {
        builder.WithOrigins(settings.Value.SignalROrigniURL)
            .AllowAnyHeader()
            .WithMethods("GET", "POST")
            .AllowCredentials();
    });
    app.UseRouting();
    app.UseAuthentication(); // this one first
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
   {
       endpoints.MapControllers();
       endpoints.MapHub<NotifyClientService>("/lockNotification");
   });
    //app.UseSignalR(routes =>
    //{
    //    routes.MapHub<NotifyClientService>("/lockNotification");
    //});
}

Update 2

CORS is also configured where the apis are deployed.

enter image description here

Raas Masood
  • 1,123
  • 14
  • 46
  • Have you tried config from doc? https://docs.microsoft.com/en-us/aspnet/core/signalr/security?view=aspnetcore-3.1 – Roar S. Nov 29 '20 at 23:26
  • i added updates as per documentation. same error. documentation doesn't say anything about "ConfigureServices" method though. – Raas Masood Nov 30 '20 at 00:10
  • Where have you deployed your app? You might need to configure CORS in the deployment, for example Azure App Service has options to configure CORS in the portal. – Brennan Nov 30 '20 at 00:20
  • ok looking into it – Raas Masood Nov 30 '20 at 00:25
  • check the update 2. it seems to be configured. – Raas Masood Nov 30 '20 at 00:29
  • You didn't check "Enable Access-Control-Allow-Credentials" – Brennan Nov 30 '20 at 00:54
  • i checked it now i get GET https://my-api-url.com/lockNotification?id=WvO0mM-oaAfKUp3m-XOLwQ 404 (Not Found) Utils.js:218 [2020-11-30T01:01:13.974Z] Error: Failed to start the transport 'ServerSentEvents': Error: Error occurred – Raas Masood Nov 30 '20 at 01:03
  • Is your app scaled out to multiple servers? Then you'll need to enable sticky sessions. Also, if you want to use WebSockets you also need to turn that on in the Azure portal. – Brennan Nov 30 '20 at 01:09
  • Sure sir looking into it. – Raas Masood Nov 30 '20 at 01:10
  • How did you call api in your angular side?Be sure add `credentials: true` in your js code.Reference:https://stackoverflow.com/a/43773028/11398810 – Rena Nov 30 '20 at 09:37
  • Yes I remember allow credentials true solved my other problems once so that is there in other api calls. But call that is failing here is “/negotiate” call that is kind of auto generated call when UI tries to make a SignalR connection. Can we set credentials to true here some how? – Raas Masood Nov 30 '20 at 11:59
  • will it be appropriate to remove CORS settings from Startup.cs since they are in app service as well . i tried that. didnt worked though. still getting 404 – Raas Masood Nov 30 '20 at 15:48

1 Answers1

0

Hello you need to configure cors exactly like I answered on this similar question:

services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy", builder => builder.WithOrigins("http://localhost:4200")
        .AllowAnyHeader()
        .AllowAnyMethod()
        .AllowCredentials()
        .SetIsOriginAllowed((host) => true));
});

Also I would recommend remove the CORS configuration that you have on Azure to test this code for CORS on the server. After configuring correctly the CORS on the backend you would not need the Azure CORS configuration that you have on Update 2.

Update

Example on configuring CORS on startup:

public void Configure(IApplicationBuilder app, HostConfiguration hostConfiguration, ILogger<Startup> logger)
{
    app.UseCors("CorsPolicy");
}

private static void AddCors(IServiceCollection services, HostConfiguration hostConfiguration)
{
    services.AddCors(options =>
    {
        options.AddPolicy("CorsPolicy", builder => builder.WithOrigins(hostConfiguration.CorsPolicy.Origins.ToArray())
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials()
            .SetIsOriginAllowed((host) => hostConfiguration.CorsPolicy.SetIsOriginAllowed));
    });
}

I call the private AddCors method on the AddAdditionalServices method.

Kiril1512
  • 2,522
  • 2
  • 10
  • 30
  • ok i am doing it but what should app.UserCors( *** look like in "Configure" function of Startup.cs ? – Raas Masood Nov 30 '20 at 16:05
  • I will update the answer to respond this question @RaasMasood – Kiril1512 Nov 30 '20 at 16:06
  • 1
    works local as expected. now deploying with "WithOrigins" set to UI's original url . i have removed allowed origins from azure portal. will let you know – Raas Masood Nov 30 '20 at 16:24
  • i am getting 404 on my HUB url . please not that i am doing " endpoints.MapHub("/lockNotification");" and i am not using app.UserSignalR*** i get 404 on my web api-base-path/lockNotification but when i click that url i see "No Connection with that ID which tells me that URL is valid. – Raas Masood Nov 30 '20 at 16:42
  • Maybe you are missing the `/` on the client side? `.withUrl(this.apiBase + '/lockNotification')` – Kiril1512 Nov 30 '20 at 16:45
  • it is trying to hit a correct url https://my-web-api.com/lockNotification?id=R0AQZfH_9w4Ua8sQp7qevA but i added console.log just to be sure and deploying. – Raas Masood Nov 30 '20 at 17:02
  • Why you are not using `app.UseSignalR(routes => { routes.MapHub("/lockNotification"); });`? – Kiril1512 Nov 30 '20 at 17:56
  • its not used in the documentation as well. https://docs.microsoft.com/en-us/aspnet/core/signalr/security?view=aspnetcore-3.1 – Raas Masood Nov 30 '20 at 17:58
  • @RaasMasood I would recommend to use it since if someday you will need to use Azure SignalR, you would need to map it like this: `app.UseAzureSignalR(routes => { routes.MapHub(NotificationsRoute); });` – Kiril1512 Nov 30 '20 at 18:08
  • i added a simple console.log and that log line is not coming up in deployed version. which tells me something else is off somewhere. i ran pipelines. and releases and used incognito window but nothing helping. let me look into this first with my devops team. – Raas Masood Nov 30 '20 at 18:34
  • @Kiril1512 can you please stop recommending `.SetIsOriginAllowed((host) => true))` unless you also include a warning about it's usage. It's basically turning off a security feature that browsers implement. Also, `UseSignalR` was obsoleted in 3.0 and removed in 5.0. Azure SignalR Service works with the simple addition of `AddAzureSignalR()`, which you can find with a quick search https://docs.microsoft.com/azure/azure-signalr/signalr-quickstart-dotnet-core#add-azure-signalr-to-the-web-app – Brennan Nov 30 '20 at 19:18
  • @Brennan thank you, I will add the warning about it's usage. – Kiril1512 Nov 30 '20 at 19:50
  • @Brennan I can't see what is the problem with the new 404 not found problem described by OP. Maybe you can help. – Kiril1512 Nov 30 '20 at 19:57
  • @Kiril1512 its deployed with my console log change. i can see that this.apiBase + '/lockNotification' url is correct. but still 404 on it. – Raas Masood Nov 30 '20 at 20:32
  • There are two common reasons you might be getting a 404 after getting an ID from negotiate. The first reason is that you have multiple servers running in your app service and don't have ARR Affinity turned on, this can cause the load balancer to send the negotiate to one server and the second request to another server. The second reason is that the client is taking over 15 seconds to receive the negotiate and then send a response so the server deletes the connection ID after a 15 second timeout. – Brennan Nov 30 '20 at 21:35