reactjscorssignalrcors-anywhere

Cannot get Signal R working due to cors issue - blocked by cors policy


I am using React with Signal R

I have a standard web application that hosts my hub.

When I send messages all works perfectly in the web page for the web application

I also have a react application that is hosted on port 3000

I changed IIS Express settings as per below

    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="X-Powered-By" value="ASP.NET" />
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type" />
      </customHeaders>
      <redirectHeaders>
        <clear />
      </redirectHeaders>
    </httpProtocol>

and my server side Startup for cors etc is below

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        services.AddCors(options =>
        {
            options.AddPolicy("cors",
                builder =>
                {
                    builder
                        .AllowAnyHeader()
                        .AllowAnyMethod()
                        .WithOrigins("http://localhost:3000");
                });
        });

        services.AddSignalR();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseCors("cors");
        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthorization();

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

On the React side I have implemented as below

import React, { Component } from 'react';
import * as signalR from '@aspnet/signalr';

class Chat extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nick: '',
      message: '',
      messages: [],
      hubConnection: null,
    };
  }

  componentDidMount = () => {
    const protocol = new signalR.JsonHubProtocol();
    const transport = signalR.HttpTransportType.WebSockets;

    const options = {
      transport,
      logMessageContent: true,
      logger: signalR.LogLevel.Trace,
    };

    // create the connection instance
    var hubConnection = new signalR.HubConnectionBuilder()
      .withUrl("http://localhost:44360/chatHub", options)
      .withHubProtocol(protocol)
      .build();

    this.setState({ hubConnection }, () => {
      this.state.hubConnection
        .start()
        .then(() => console.log('Connection started!'))
        .catch(err => console.log('Error while establishing connection :('));

      this.state.hubConnection.on('SendMessage', (user, message) => {
        const text = `${user}: ${message}`;
        const messages = this.state.messages.concat([text]);

        console.log('ssss');

        this.setState({ messages });
      });
    });
  }

  render() {
    return (
      <div>
        <br />

        <div>
          {this.state.messages.map((message, index) => (
            <span style={{display: 'block'}} key={index}> {message} </span>
          ))}
        </div>
      </div>
    );
  }
}

export default Chat;

As you can see I have connected to the exact port where my server application is

I get an entry in the log to say I am connected

However, I never actually get any messages?

My hub in the web application is shown below

"use strict";

var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();

//Disable send button until connection is established
document.getElementById("sendButton").disabled = true;

connection.on("ReceiveMessage", function (user, message) {
    var msg = message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    var encodedMsg = user + " says " + msg;
    var li = document.createElement("li");
    li.textContent = encodedMsg;
    document.getElementById("messagesList").appendChild(li);
});

connection.start().then(function () {
    document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
    return console.error(err.toString());
});

document.getElementById("sendButton").addEventListener("click", function (event) {
    var user = document.getElementById("userInput").value;
    var message = document.getElementById("messageInput").value;
    connection.invoke("SendMessage", user, message).catch(function (err) {
        return console.error(err.toString());
    });
    event.preventDefault();
});

I thought I had resolved the Cors issues but when I left the web page open for a while I got the error

Access to XMLHttpRequest at 'http://localhost:44360/chatHub/negotiate' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Can anyone see what I have done wrong?


Solution

  • The problem is that you've defined origins both in IIS config and in code. You should only specify origins in IIS config if (1) you only trust a single origin for all applications on the server, or (2) you trust all origins for all applications on the server.

    When specified in the config at server level, every http context in the pipeline has the Access-Control-Allow-Origin header added. Then, the WithOrigins(<origin>) method appends another value to it.

    https://www.w3.org/TR/cors/

    6.4 Implementation Considerations This section is non-normative.

    Resources that wish to enable themselves to be shared with multiple Origins but do not respond uniformly with "*" must in practice generate the Access-Control-Allow-Origin header dynamically in response to every request they wish to allow. As a consequence, authors of such resources should send a Vary: Origin HTTP header or provide other appropriate control directives to prevent caching of such responses, which may be inaccurate if re-used across-origins.