I'm using Vue + ts + @microsoft/signalr in my project. I want to develop a chat with SignalR. I'm also using C# + ASP.NET Core on the backend + docker.
So, I have written the following code on my backend:
using DomovoiBackend.API.Auth.ServiceDecorators;
using DomovoiBackend.API.Controllers;
using DomovoiBackend.API.JsonInheritance;
using DomovoiBackend.Application;
using DomovoiBackend.Application.Services.CounterAgentServices.Interfaces;
using DomovoiBackend.Persistence;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSignalR();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1",
new OpenApiInfo()
{
Title = "DomovoiBackend.API",
Version = "v1"
});
options.UseAllOfToExtendReferenceSchemas();
options.UseAllOfForInheritance();
options.UseOneOfForPolymorphism();
});
var inheritanceConfigurations =
new AssemblyInheritanceConfiguration()
.CreateAllConfigurations();
builder.Services.AddControllers().AddNewtonsoftJson(
options =>
{
foreach(var inheritanceConfiguration in inheritanceConfigurations)
options.SerializerSettings.Converters.Add(inheritanceConfiguration);
});
builder.Services.AddApplicationLayer()
.AddMappers()
.AddPersistence(builder.Configuration)
.CreateDatabase()
.FillDatabase();
builder.Services.Decorate<ICounterAgentService, AuthCounterAgentService>();
builder.Services.AddHttpContextAccessor();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddAuthorization();
builder.Services.AddSession();
builder.Services.AddCors();
builder.Services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.SlidingExpiration = true;
options.LoginPath = "/CounterAgent/Login";
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
});
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AuthenticatedCounterAgent", policy => policy.RequireClaim("CounterAgentId"));
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseCors(policyBuilder =>
{
policyBuilder.WithOrigins("http://localhost:5173")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetIsOriginAllowedToAllowWildcardSubdomains()
.SetIsOriginAllowed((host) => true);
});
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapHub<ChatHub>("/chat");
app.Run();
public partial class Program;
ChatHub class:
[Authorize]
public class ChatHub : Hub
{
[Authorize]
public async Task Send(string message, string idReceiver)
{
var nameSender = Context.User?.Identity?.Name;
if (Clients.User(idReceiver) == null)
throw new UnknownUser($"User with id \"{idReceiver}\" does not exist in the system");
await Clients.User(idReceiver).SendAsync("Receive", message, nameSender);
await Clients.Caller.SendAsync("NotifySendMethod", "Ok");
}
}
and frontend:
import { HttpTransportType, HubConnection, HubConnectionBuilder } from "@microsoft/signalr";
import { inject, singleton } from "tsyringe";
import { Chat } from "../../application/useCases/chat";
import { Message } from "../../domain/chat/message";
import { MessageStatus } from "../../domain/enums/chatEnum";
import { CounterAgent } from "../../domain/counteragents/counteragent";
import { CounteragentViewModel } from "../../viewModel/CounteragentViewModel";
import { ICouterAgentMapper } from "../../mappers/interfaces/couteragentMapperInterface";
@singleton()
export class ChatService {
private readonly _connection: HubConnection;
private _listener!: Chat;
public constructor(@inject("chatURL") private readonly _baseURL: string,
@inject("ICouterAgentMapper") private readonly _userMapper: ICouterAgentMapper) {
this._connection = new HubConnectionBuilder()
.withUrl(this._baseURL, {
headers: {
'Access-Control-Allow-Origin': '*',
},
withCredentials: true,
transport: HttpTransportType.WebSockets,
})
.build();
this._connection.on("Receive", (text: string, idSender: string) => this.receiveMessage(text, idSender));
}
public start(idCompanion :string, user: CounteragentViewModel): void {
let counteragent = this._userMapper.mapViewModelToCouterAget(user);
this._listener = new Chat(this, true, idCompanion, counteragent);
this._connection.start();
}
public close(): void {
this._connection.stop()
}
public get state(): string {
return this._connection.state.toString();
}
public get messages(): Message[] {
return this._listener.messages;
}
public async sendMessage(text: string): Promise<void> {
let message = new Message("", text, this._listener.idCompanion, this._listener.user.id!);
try {
let response = await this._connection.invoke("Send", message.text, message.recieverId);
message.status = MessageStatus.Send;
}
catch(e){
message.status = MessageStatus.NotSend;
throw(e);
}
finally {
this._listener.addMessage(message);
}
}
public receiveMessage(text: string, idSender: string): void {
let message = new Message("", text, idSender, this._listener.user.id!);
message.status = MessageStatus.Recieve;
this._listener.addMessage(message);
}
}
I use ChatService in ChatView.vue:
//...
export default defineComponent({
components: { Header},
data() {
return {
store: store,
chatService: {} as ChatService,
//...
};
},
computed: {
},
mounted() {
//...
this.load_data();
},
methods: {
load_data(){
let chatService = container.resolve(ChatService);
this.chatService = chatService;
chatService.start(this.user.id, this.store.state.user!);
this.messages = this.chatService.messages;
},
//...
},
})
</script>
After load_data() method and connection.start() i get this message:
Access to fetch at 'http://localhost:8181/chat/negotiate?negotiateVersion=1' from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
@microsoft_signalr.js?v=12bba02c:2110 Uncaught (in promise) Error: Failed to complete negotiation with the server: TypeError: Failed to fetch at HttpConnection._getNegotiationResponse (@microsoft_signalr.js?v=12bba02c:2110:29)
ChatService.ts:35 [2024-06-03T18:17:15.726Z] Error: Failed to start the connection: Error: Failed to complete negotiation with the server: TypeError: Failed to fetch
ChatService.ts:35 [2024-06-03T18:17:15.725Z] Error: Failed
to complete negotiation with the server: TypeError: Failed to fetch
I have used different cors policy settings on backend and different headers in connectionBuilder.WithUrl(...), but it does not help me.
You are using SetIsOriginAllowed
and WithOrigins
together. It will face cors issue, for more details, you can check this thread.
Change your CORS
setting like below.
app.UseCors(policyBuilder =>
{
policyBuilder.WithOrigins("http://localhost:5173")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
//.SetIsOriginAllowedToAllowWildcardSubdomains()
//.SetIsOriginAllowed((host) => true);
});
And I also find some middleware order issue in your code.
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
//app.UseAuthentication();
//app.UseAuthorization();
//app.UseSession();
app.UseCors(policyBuilder =>
{
policyBuilder.WithOrigins("http://localhost:5173")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
//.SetIsOriginAllowedToAllowWildcardSubdomains()
//.SetIsOriginAllowed((host) => true);
});
app.UseAuthentication();
app.UseAuthorization();
// move it to here
app.UseSession();
app.MapControllers();
app.MapHub<ChatHub>("/chat");
app.Run();