using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using SignalRAuthenticationSample.Data; using SignalRAuthenticationSample.Hubs; namespace SignalRAuthenticationSample { public class Startup { // We use a key generated on this server during startup to secure our tokens. // This means that if the app restarts, existing tokens become invalid. It also won't work // when using multiple servers. public static readonly SymmetricSecurityKey SecurityKey = new SymmetricSecurityKey(Guid.NewGuid().ToByteArray()); public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. #region snippet public void ConfigureServices(IServiceCollection services) { services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); services.AddAuthentication(options => { // Identity made Cookie authentication the default. // However, we want JWT Bearer Auth to be the default. options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { // Configure the Authority to the expected value for your authentication provider // This ensures the token is appropriately validated options.Authority = /* TODO: Insert Authority URL here */; // We have to hook the OnMessageReceived event in order to // allow the JWT authentication handler to read the access // token from the query string when a WebSocket or // Server-Sent Events request comes in. // Sending the access token in the query string is required when using WebSockets or ServerSentEvents // due to a limitation in Browser APIs. We restrict it to only calls to the // SignalR hub in this code. // See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging // for more information about security considerations when using // the query string to transmit the access token. options.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; // If the request is for our hub... var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/hubs/chat"))) { // Read the token out of the query string context.Token = accessToken; } return Task.CompletedTask; } }; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddSignalR(); // Change to use Name as the user identifier for SignalR // WARNING: This requires that the source of your JWT token // ensures that the Name claim is unique! // If the Name claim isn't unique, users could receive messages // intended for a different user! services.AddSingleton(); // Change to use email as the user identifier for SignalR // services.AddSingleton(); // WARNING: use *either* the NameUserIdProvider *or* the // EmailBasedUserIdProvider, but do not use both. } #endregion // This method gets called by the runtime. Use this method to configure the // HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAuthentication(); app.UseMvc(); app.UseSignalR(routes => { routes.MapHub("/hubs/chat"); }); } } }