I have a project that involves using SignalR from a JavaScript application. Since the client is coming from a browser, my application employs JWT authentication for the SignalR client.
In my WebAPI, I have JWT Bearer authentication configured. However, when attempting to use SignalR hub with authentication, I encountered errors, and essentially, I wasn't sure how to call the secure SignalR endpoint. After extensive research on the internet, I have found a solution that I'm going to share in this post so let's discuess about How to call signalr secure endpoint using jwt token core c# server.
In this post we are going to discuess the below point
[Authorize]
public class ChatHub : Hub
{
private readonly IServiceScopeFactory _scopeFactory;
public ChatHub(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public override Task OnDisconnectedAsync(Exception exception)
{
using (var scope = _scopeFactory.CreateScope())
{
var _dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
var userId = new Guid(Context.User.GetLoggedInUserClaim(ClaimType.UserId));
var user = _dbContext.Users.FirstOrDefault(a => a.Id == userId);
user.IsOnline = false;
user.LastDisconnectedAt = DateTime.UtcNow;
_dbContext.SaveChanges();
Debug.WriteLine("Client disconnected: " + Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
}
}
public override Task OnConnectedAsync()
{
using (var scope = _scopeFactory.CreateScope())
{
Debug.WriteLine("Client connected: " + Context.ConnectionId);
var _dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
var userId = new Guid(Context.User.GetLoggedInUserClaim(ClaimType.UserId));
var user = _dbContext.Users.FirstOrDefault(a => a.Id == userId);
user.ConnectionId = Context.ConnectionId;
user.IsOnline = true;
user.LastConnectedAt = DateTime.UtcNow;
_dbContext.SaveChanges();
return base.OnConnectedAsync();
}
}
public async Task SendNotification(string userId)
{
if (Clients != null)
{
await Clients.Group(userId.ToLower()).SendAsync("Notification", "New notification recived!");
}
}
}
Here is my Program.cs file
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "PetsDevoteeBack", Version = "v1" });
var securitySchema = new OpenApiSecurityScheme
{
Description = @"JWT Authorization header using the Bearer scheme. <br>
Enter 'Bearer' [space] and then your token in the text input below. <br>
Example: 'Bearer 12345abcdef'",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "bearer",
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
};
c.AddSecurityDefinition("Bearer", securitySchema);
var securityRequirement = new OpenApiSecurityRequirement
{
{ securitySchema, new[] { "Bearer" } }
};
c.AddSecurityRequirement(securityRequirement);
});
builder.Services.AddSignalR();
builder.Services.AddSingleton<ChatHub>();
builder.Services.AddSingleton<JWTManagerRepository>();
builder.Services.AddSingleton<DbThreadLogic>();
// JWT
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
var Key = Encoding.UTF8.GetBytes(builder.Configuration["JWT:Key"]);
o.SaveToken = true;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["JWT:Issuer"],
ValidAudience = builder.Configuration["JWT:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Key)
};
});
builder.Services.AddAuthorization(x =>
{
x.AddPolicy(AccessLevels.GlobalKey, policy => policy.RequireClaim("Type", ((int)Permissions.Global).ToString()));
});
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogle(options =>
{
options.ClientId = builder.Configuration["Authentication:Google:ClientId"];
options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];
});
builder.Services.AddCors(p => p.AddDefaultPolicy(builder =>
{
builder.WithOrigins("*").AllowAnyMethod().AllowAnyHeader();
}));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
else
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors(builder => builder
.AllowAnyHeader()
.AllowAnyMethod()
.SetIsOriginAllowed((host) => true)
.AllowCredentials()
);
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles();
app.MapControllers();
app.MapHub<ChatHub>("/Chating");
app.Run();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.0.1/css/toastr.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.0.1/js/toastr.js"></script>
<script type="text/javascript">
// Authenticate user and obtain JWT token
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJUeXBlIjoiMCIsIlVzZXJJZCI6IjYxYTRmZjYwLWY2ZWYtZWUxMS05Y2JiLWE0YmYwMTZjZTU2ZCIsIm5iZiI6MTcxMjA2MTg4NiwiZXhwIjoxNzEyMTQ4Mjg2LCJpYXQiOjE3MTIwNjE4ODZ9.SPYHa4BnQlF-GRAeq7pyOlEZkpuJ3eXwcxIzcJCK2ts";
const connection = new signalR.HubConnectionBuilder()
.withUrl("https://localhost:7140/Chating",{
accessTokenFactory: () => token
})
.configureLogging(signalR.LogLevel.Information)
.build();
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};
connection.onclose(async () => {
await start();
});
// Listen for events
connection.on("Notification", async (message) => {
alert(message)
});
// Handle token expiration and renew the token if necessary
// Example logic to renew token
function renewToken() {
// Implement token renewal logic in case if token expire
}
// Renew token every 60 minutes
setInterval(renewToken, 60 * 60 * 1000);
// Start the connection.
start();
</script>
Above code exanple establishes a connection to the SignalR hub using the provided JWT token for authentication. It also listens for events emitted by the hub and handles token expiration by renewing the token periodically.