Use SwaggerUI to authenticate against IdentiyServer 4 with own protected API Endpoints - asp.net-core

I'm relatively new to IdentityServer4 but I went through the docs and managed to set it up.
In my scenario I want to use IdentityServer4 and also protect additional endpoints within the identity server. I followed the documentation to use client credentials as described here. Instead of using a separate API I followed the docs here to protect the API endpoints within the identity server which works as expected.
I also want to use Swagger to provide documentation for these additional endpoints. But unfortunately I don't know how to properly setup the configuration. The "Authorize" button appears and I can enter the client and secret and also login which works fine but whenever I try to execute the action with Swagger I get a 401 Unauthorized error back.
My sample project looks like this:
Program.cs
public class Program
{
public static int Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information)
.Enrich.FromLogContext()
// uncomment to write to Azure diagnostics stream
//.WriteTo.File(
// #"D:\home\LogFiles\Application\identityserver.txt",
// fileSizeLimitBytes: 1_000_000,
// rollOnFileSizeLimit: true,
// shared: true,
// flushToDiskInterval: TimeSpan.FromSeconds(1))
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code)
.CreateLogger();
try
{
Log.Information("Starting host...");
CreateHostBuilder(args).Build().Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly.");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Startup.cs
public class Startup
{
public IWebHostEnvironment Environment { get; }
public Startup(IWebHostEnvironment environment)
{
Environment = environment;
}
public void ConfigureServices(IServiceCollection services)
{
services
.AddControllers();
services
.AddIdentityServer(options =>
{
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients);
services.AddLocalApiAuthentication();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo {Title = "Protected API", Version = "v1"});
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
In = ParameterLocation.Header,
Flows = new OpenApiOAuthFlows
{
ClientCredentials = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri("https://localhost:5001/connect/authorize"),
TokenUrl = new Uri("https://localhost:5001/connect/token"),
Scopes = new Dictionary<string, string>
{
{"IdentityServerApi", "Demo API - full access"}
}
}
}
});
});
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
options.OAuthClientId("client");
options.OAuthAppName("Demo API - Swagger");
options.OAuthUsePkce();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers()
.RequireAuthorization(IdentityServerConstants.LocalApi.PolicyName); // auth attribute for all controllers!
});
}
}
Config.cs
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId()
};
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope(IdentityServerConstants.LocalApi.ScopeName, "My API"),
};
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { IdentityServerConstants.LocalApi.ScopeName }
}
};
}
LocalApiController.cs
[ApiController]
[Route("localApi")]
public class LocalApiController : ControllerBase
{
[HttpGet]
public ActionResult<string> Get()
{
return Ok("ok");
}
}

I forgot to add authentication information to the swagger documentation.
After adding the following filter and registering it like described here everything works
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var hasAuthorize =
context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any()
|| context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
if (hasAuthorize)
{
operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[
new OpenApiSecurityScheme {Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "oauth2"}
}
] = new[] {"api1"}
}
};
}
}
}

Related

How to seed data for role base authentication using Microsoft Identity Framework in asp.net 6.0

I have created two static class
DefaultRoles.cs
using DCMS.Web.Constants;
using Microsoft.AspNetCore.Identity;
namespace DCMS.Web.Seeds
{
public class DefaultRoles
{
public static async Task SeedAsync(UserManager<IdentityUser> userManager,
RoleManager<IdentityRole> roleManager)
{
await roleManager.CreateAsync(new IdentityRole(Roles.SuperAdmin.ToString()));
await roleManager.CreateAsync(new IdentityRole(Roles.Admin.ToString()));
await roleManager.CreateAsync(new IdentityRole(Roles.Manager.ToString()));
await roleManager.CreateAsync(new IdentityRole(Roles.SalesStaff.ToString()));
await roleManager.CreateAsync(new IdentityRole(Roles.Basic.ToString()));
}
}
and my DefaultUser.cs Class Like-
using DCMS.Web.Constants;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;
namespace DCMS.Web.Seeds
{
public static class DefaultUsers
{
public static async Task SeedBasicUserAsync(UserManager<IdentityUser> userManager,
RoleManager<IdentityRole> roleManager)
{
// Seed Basic User
var defaultUser = new IdentityUser
{
UserName = "dcmsbasicuser#gmail.com",
Email = "dcmsbasicuser#gmail.com",
EmailConfirmed = true,
PhoneNumberConfirmed = true
};
if (userManager.Users.All(u => u.Id != defaultUser.Id))
{
var user = await userManager.FindByEmailAsync(defaultUser.Email);
if (user == null)
{
await userManager.CreateAsync(defaultUser, "100%Pa$$word");
await userManager.AddToRoleAsync(defaultUser, Roles.Basic.ToString());
}
}
}
public static async Task SeedSuperAdminUserAsync(UserManager<IdentityUser> userManager,
RoleManager<IdentityRole> roleManager)
{
// Seed SuperAdmin User
var defaultUser = new IdentityUser
{
UserName = "dcmssuperadminuser#gmail.com",
Email = "dcmssuperadminuser#gmail.com",
EmailConfirmed = true,
PhoneNumberConfirmed = true
};
if (userManager.Users.All(u => u.Id != defaultUser.Id))
{
var user = await userManager.FindByEmailAsync(defaultUser.Email);
if (user == null)
{
await userManager.CreateAsync(defaultUser, "100%SuperAdminPa$$word");
await userManager.AddToRoleAsync(defaultUser, Roles.Basic.ToString());
await userManager.AddToRoleAsync(defaultUser, Roles.Admin.ToString());
await userManager.AddToRoleAsync(defaultUser, Roles.SuperAdmin.ToString());
}
await roleManager.SeedClaimsForSuperAdmin();
}
}
private static async Task SeedClaimsForSuperAdmin(this RoleManager<IdentityRole> roleManager)
{
var adminRole = await roleManager.FindByNameAsync(Roles.SuperAdmin.ToString());
await roleManager.AddPermissionClaim(adminRole, "Products");
}
public static async Task AddPermissionClaim(this RoleManager<IdentityRole> roleManager, IdentityRole role, string module)
{
var allClaims = await roleManager.GetClaimsAsync(role);
var allPermissions = Permissions.GeneratePermissionsForModule(module);
foreach (var permission in allPermissions)
{
if(!allClaims.Any(a => a.Type=="Permission" && a.Value == permission))
{
await roleManager.AddClaimAsync(role, new Claim("Permission", permission));
}
}
}
}
}
Now I want to Inject those class into program.cs class. my program.cs class like-
using Autofac;
using Autofac.Extensions.DependencyInjection;
using DCMS.Web.Data;
using DCMS.Web.Seeds;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Serilog;
using Serilog.Events;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => {
containerBuilder
.RegisterModule(new WebModule());
});
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddControllersWithViews();
builder.Host.UseSerilog((ctx, lc) => lc
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.ReadFrom.Configuration(builder.Configuration));
try
{
var app = builder.Build();
Log.Information("Application Starting up");
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}
How can I do? Basically I want to seed data from those class for user authentication. I am using asp.net 6.0. please help me to solve the problem
Just add this code to program.cs file
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var userManager = services.GetRequiredService<UserManager<IdentityUser>>();
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
await DefaultRoles.SeedAsync(userManager, roleManager);
await DefaultUsers.SeedBasicUserAsync(userManager, roleManager);
await DefaultUsers.SeedSuperAdminUserAsync(userManager, roleManager);
Log.Information("Identity User Data Seeding finished");
}
catch (Exception)
{
throw;
}
}
Now program.cs file will be like this-
using Autofac;
using Autofac.Extensions.DependencyInjection;
using DCMS.Web.Data;
using DCMS.Web.Seeds;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Serilog;
using Serilog.Events;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => {
containerBuilder
.RegisterModule(new WebModule());
});
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddControllersWithViews();
builder.Host.UseSerilog((ctx, lc) => lc
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.ReadFrom.Configuration(builder.Configuration));
try
{
var app = builder.Build();
Log.Information("Application Starting up");
// Need to add for data seeding Start
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var userManager = services.GetRequiredService<UserManager<IdentityUser>>();
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
await DefaultRoles.SeedAsync(userManager, roleManager);
await DefaultUsers.SeedBasicUserAsync(userManager, roleManager);
await DefaultUsers.SeedSuperAdminUserAsync(userManager, roleManager);
Log.Information("Identity User Data Seeding finished");
}
catch (Exception)
{
throw;
}
}
// Need to add for data seeding End
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}

Certificate Authentication events not triggered in ASP .NET Core 3.1 API

I want to implement certificate authentication on my .NET Core 3.1 API. I followed the steps outlined by Microsoft here:https://learn.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-5.0
But, I don't think the events "OnCertificateValidated" and "OnAuthenticationFailed" even fire. Breakpoints are never hit, and I even tried adding code that should break in there just to test, but it never fails (most likely because it never reaches there)
I'm trying to validate the Client Certificate CN and I want to only allow certain CNs to be able to call my API.
You can find my code below. What am I doing wrong?
Startup
public class Startup
{
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.AllowedCertificateTypes = CertificateTypes.All;
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService =
context.HttpContext.RequestServices.GetRequiredService<ICertificateValidationService>();
if (validationService.ValidateCertificate(context.ClientCertificate))
{
context.Success();
}
else
{
context.Fail($"Unrecognized client certificate: {context.ClientCertificate.GetNameInfo(X509NameType.SimpleName, false)}");
}
int test = Convert.ToInt32("test");
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
int test = Convert.ToInt32("test");
context.Fail($"Invalid certificate");
return Task.CompletedTask;
}
};
});
services.AddHealthChecks();
services.AddControllers(setupAction =>
{
setupAction.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status406NotAcceptable));
setupAction.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status500InternalServerError));
setupAction.ReturnHttpNotAcceptable = true;
}).AddXmlDataContractSerializerFormatters();
services.AddScoped<IJobServiceRepository, JobServiceRepository>();
services.AddScoped<ICaptureServiceRepository, CaptureServiceRepository>();
services.Configure<KTAEndpointConfig>(Configuration.GetSection("KTAEndpointConfig"));
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddVersionedApiExplorer(setupAction =>
{
setupAction.GroupNameFormat = "'v'VV";
});
services.AddApiVersioning(setupAction =>
{
setupAction.AssumeDefaultVersionWhenUnspecified = true;
setupAction.DefaultApiVersion = new ApiVersion(1, 0);
setupAction.ReportApiVersions = true;
});
var apiVersionDecriptionProvider = services.BuildServiceProvider().GetService<IApiVersionDescriptionProvider>();
services.AddSwaggerGen(setupAction =>
{
foreach (var description in apiVersionDecriptionProvider.ApiVersionDescriptions)
{
setupAction.SwaggerDoc($"TotalAgilityOpenAPISpecification{description.GroupName}", new Microsoft.OpenApi.Models.OpenApiInfo()
{
Title = "TotalAgility API",
Version = description.ApiVersion.ToString(),
Description = "Kofax TotalAgility wrapper API to allow creating KTA jobs and uploading documents",
Contact = new Microsoft.OpenApi.Models.OpenApiContact()
{
Email = "shivam.sharma#rbc.com",
Name = "Shivam Sharma"
}
});
setupAction.OperationFilter<AddRequiredHeaderParameter>();
var xmlCommentsFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlCommentsFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentsFile);
setupAction.IncludeXmlComments(xmlCommentsFullPath);
}
setupAction.DocInclusionPredicate((documentName, apiDescription) =>
{
var actionApiVersionModel = apiDescription.ActionDescriptor
.GetApiVersionModel(ApiVersionMapping.Explicit | ApiVersionMapping.Implicit);
if (actionApiVersionModel == null)
{
return true;
}
if (actionApiVersionModel.DeclaredApiVersions.Any())
{
return actionApiVersionModel.DeclaredApiVersions.Any(v =>
$"TotalAgilityOpenAPISpecificationv{v.ToString()}" == documentName);
}
return actionApiVersionModel.ImplementedApiVersions.Any(v =>
$"TotalAgilityOpenAPISpecificationv{v.ToString()}" == documentName);
});
});
services.AddHttpsRedirection((httpsOpts) =>
{
//httpsOpts.HttpsPort = 443;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider apiVersionDescriptionProvider)
{
app.UseApiExceptionHandler();
app.UseHeaderLogContextMiddleware();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger(setupAction =>
{
setupAction.SerializeAsV2 = true;
});
app.UseSwaggerUI(setupAction =>
{
foreach (var decription in apiVersionDescriptionProvider.ApiVersionDescriptions)
{
setupAction.SwaggerEndpoint($"/swagger/TotalAgilityOpenAPISpecification{decription.GroupName}/swagger.json",
decription.GroupName.ToUpperInvariant());
}
//setupAction.SwaggerEndpoint("/swagger/TotalAgilityOpenAPISpecification/swagger.json",
//"TotalAgility API");
setupAction.RoutePrefix = "";
});
app.UseSerilogRequestLogging();
app.UseHeaderValidation();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireAuthorization();
endpoints.MapHealthChecks("/health");
});
}
}
CertificateValidationService
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace TotalAgility_API.Services
{
public class CertificateValidationService : ICertificateValidationService
{
private readonly IConfiguration _configuration;
private readonly ILogger<CertificateValidationService> _logger;
public CertificateValidationService(IConfiguration configuration, ILogger<CertificateValidationService> logger)
{
_logger = logger;
_configuration = configuration;
}
public bool ValidateCertificate(X509Certificate2 clientCert)
{
List<string> allowedCNs = new List<string>();
_configuration.GetSection("AllowedClientCertCNList").Bind(allowedCNs);
string cn = clientCert.GetNameInfo(X509NameType.SimpleName, false);
if (allowedCNs.Contains(cn))
{
return true;
}
else
{
_logger.LogWarning("Invalid Cert CN: {CN}", cn);
return false;
}
}
}
}
appsettings.json
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
}
},
"AllowedHosts": "*",
"AllowedClientCertCNList": [
"1",
"2",
"3",
"4"
]
}
Any help would be appreciated here. I'm new to this and I'm don't know how to proceed.
I had a similar issue on IIS Express where only "OnChallenge" fired but not "OnCertificateValidated" or "OnAuthenticationFailed", and kept getting 403 status responses.
This answer from a different question solved it for my case (IIS Express). Set the access sslFlags in .vs\config\applicationhost.config to use client certificate:
<access sslFlags="Ssl, SslNegotiateCert, SslRequireCert" />

My AspnetCore application does not receive publications from a .net core console application via SignalR

My AspnetCore application does not receive publications from a .net core console application via SignalR
I have an aspnet core 2.1 web application that contains a SignalR Hub.
I need to make a .net core 2.1 console application that send information to my hub on my web system.
I did a development here, however when I run my console app, no exception appears, but my web application does not receive the information.
See what I've done so far.
Aspnet Core 2.1
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddSession();
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSignalR();
services.AddMvc(
config => {
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
config.Filters.Add(new AuthorizeFilter(policy));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.LoginPath = "/Home/Login";
options.LogoutPath = "/Usuario/Logout";
options.SlidingExpiration = true;
});
//provedor postgresql
services.AddEntityFrameworkNpgsql()
.AddDbContext<MesContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("MesContext")));
services.AddScoped<SeedingService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, SeedingService seedingService)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
seedingService.Seed();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSignalR(routes =>
{
routes.MapHub<MesHub>("/MesHub");
});
}
}
View Index
//teste
// ATUALIZA LISTAGEM
connection.on("teste", function (sucesso, msg) {
if (sucesso) {
console.log(msg);
}
});
Hub
public class MesHub : Hub
{
public static ConcurrentDictionary<string, List<string>> ConnectedUsers = new ConcurrentDictionary<string, List<string>>();
public void SendAsync(string message)
{
// Call the addMessage method on all clients
Clients.All.SendAsync("teste", true, message);
}
public override Task OnConnectedAsync()
{
string name = Context.User.Identity.Name;
Groups.AddToGroupAsync(Context.ConnectionId, name);
return base.OnConnectedAsync();
}
}
Console aplication
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Teste Renan!");
var url = "https://localhost:44323/meshub";
var connection = new HubConnectionBuilder()
.WithUrl($"{url}")
.WithAutomaticReconnect() //I don't think this is totally required, but can't hurt either
.Build();
var t = connection.StartAsync();
Console.WriteLine("conected!");
//Wait for the connection to complete
t.Wait();
Console.WriteLine(connection.State);
//connection.SendAsync("SendAsync", "renan");
connection.InvokeAsync<string>("SendAsync", "HELLO World ").ContinueWith(task => {
if (task.IsFaulted)
{
Console.WriteLine("There was an error calling send: {0}",
task.Exception.GetBaseException());
}
else
{
Console.WriteLine(task.Result);
}
});
Console.WriteLine("enviado!");
}
}
In the console app I add the Microsoft.aspnetCore.SignalR.Client package
Signalr works very well within the aspnet core web system, as it is not receiving information externally.
I'm forgetting something? what am I doing wrong?
I define same hub method and do a test to invoke it from the console client, which work as expected.
Besides, I test it with my SignalR console client app, it works well too. If possible, you can test it and check if it works for you.
static void Main(string[] args)
{
Console.WriteLine("Client App Starts...");
Program p = new Program();
p.Myfunc().Wait();
Console.ReadLine();
}
public async Task Myfunc()
{
HubConnection connection = new HubConnectionBuilder()
.WithUrl("https://localhost:44323/meshub")
.Build();
connection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await connection.StartAsync();
};
await connection.StartAsync();
try
{
await connection.InvokeAsync("SendAsync", "HELLO World ");
//await connection.InvokeAsync("SendMessage", "console app", "test mes");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Test Result
Updated:
in my Hub I have an OnConnectedAsync method where the name of the group is informed
You can try to update the OnConnectedAsync method as below, then check if your SignalR console client app can connect to hub and communicate with web client well.
public override async Task OnConnectedAsync()
{
string name = Context.User.Identity.Name;
if (name != null)
{
await Groups.AddToGroupAsync(Context.ConnectionId, name);
}
await base.OnConnectedAsync();
}

ASP.NET Core 3.1 SignalR connects but doesnt' work

I am migrating an ASP.NET Web API 2 service to ASP.NET Core 3.1 Web API, and I already resolved all dependencies and problems, but I'm not getting the SignalR.
Strange things:
I can connect but I'm receiving any message:
chrome console connection
The id about this connection is always 0(in asp.net framework, was always an unique code)
I put a custom authorize in the hub:
Code:
using Flow2.Api.Security;
using Microsoft.AspNetCore.SignalR;
namespace Flow2.Api.Hubs
{
[AuthorizeSignalR]
public class HubNotification : Hub
{
public void Notificar()
{
Clients.All.SendAsync("broadcastMessage", "oi", "olá");
}
}
}
With a break point, but the breakpoint is not getting hit: print authorize with breakpoint
My startup configuration:
using System;
using System.Globalization;
using System.IO.Compression;
using System.Runtime.InteropServices;
using DataTables.AspNet.AspNetCore;
using Flow2.Api.Hangfire;
using Flow2.Api.Helpers;
using Flow2.Api.Hubs;
using Flow2.Api.JsonConverters;
using Flow2.Api.Security;
using Flow2.Api.Security.SignalR;
using Flow2.Mappers;
using Flow2.SharedKernel.Config;
using Hangfire;
using Hangfire.SqlServer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Flow2.CrossCutting.Startup;
using Flow2.Domain.Interfaces;
using Flow2.Domain.Outros.Arquivo.Interfaces;
using Flow2.SharedKernel;
using Flow2.SharedKernel.Events;
using Flow2.SharedKernel.Helpers;
using Flow2.SharedKernel.Helpers.Email;
using Hangfire.MemoryStorage;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.AspNetCore.SignalR;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Swashbuckle.AspNetCore.SwaggerUI;
namespace Flow2.Api
{
using System.Linq;
using Microsoft.OpenApi.Models;
using Swagger.Filters;
using Swagger;
public class Startup
{
private string _policyCors { get; } = "MyPolicy";
private readonly IConfiguration _configuration;
private readonly IWebHostEnvironment _webHostEnvironment;
public Startup(IConfiguration configuration, IWebHostEnvironment appEnv)
{
_configuration = configuration;
_webHostEnvironment = appEnv;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services
.ConfigurarControllers()
.ConfigurarCors(_policyCors)
.AdicionarSwagger(_webHostEnvironment)
.AdicionarInjecaoDependencia(_configuration)
.AdicionarHangfire(_configuration, _webHostEnvironment)
//.ConfigurarCompressaoResposta()
.AdicionarAutenticacao(_configuration, _webHostEnvironment)
.AdicionarMailService(_configuration)
.AddSignalR(options => { options.EnableDetailedErrors = true; });
//services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
services.RegisterDataTables();
if (_webHostEnvironment.IsProduction())
services.AddApplicationInsightsTelemetry();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IRecurringJobManager recurringJobManager, IServiceProvider serviceProvider)
{
app.UseCors(_policyCors);
var scope = serviceProvider.CreateScope();
DomainEvent.Container = new DomainEventsContainer(scope);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
TelemetryConfiguration.Active.DisableTelemetry = true;
TelemetryDebugWriter.IsTracingDisabled = true;
}
else
{
app.UseHsts();
app.UseHttpsRedirection();
ConfigurarHangfire(app, recurringJobManager, serviceProvider);
}
app.UseStaticFiles();
app.UseRouting();
//app.UseResponseCompression();
//app.UseMiddleware<WebSocketsMiddleware>();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<HubNotification>("/hubnotifications");
});
app.UseSwagger(c =>
{
c.RouteTemplate = "docs/{documentname}/swagger.json";
});
app.UseSwaggerUI(c =>
{
c.InjectJavascript("/custom.js");
if (_webHostEnvironment.IsDevelopment())
c.SwaggerEndpoint("/docs/local/swagger.json", "Flow local");
c.SwaggerEndpoint("/docs/v1/swagger.json", "Flow v1");
c.SwaggerEndpoint("/docs/v2/swagger.json", "Flow v2");
c.InjectStylesheet("/custom.css");
c.RoutePrefix = "docs";
c.DisplayRequestDuration();
c.DocExpansion(DocExpansion.List);
c.DefaultModelRendering(ModelRendering.Example);
});
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("pt-BR");
AutomapperConfig.RegisterMappings();
DelegateCompilerConfig.ConfigurarDelegateCompiler();
}
private void ConfigurarHangfire(IApplicationBuilder app, IRecurringJobManager recurringJobManager, IServiceProvider serviceProvider)
{
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new HangfireAuthorizationFilter() },
});
var azureManagerBackupService = serviceProvider.GetService<IAzureManageBackupService>();
var atualizadorBancos = serviceProvider.GetService<IAtualizadorBancosDeDadosService>();
var timeZoneInfo = TimezoneHelper.ObterTimezoneBrasil();
recurringJobManager.AddOrUpdate(
"RemoverNotificacoesAntigas",
() => atualizadorBancos.RemoverNotificacoesAntigas(30),
"0 1 * * *",
timeZoneInfo
);
recurringJobManager.AddOrUpdate(
"RemoverBackupsAzure",
() => azureManagerBackupService.RemoverPageBlobsAntigos(15),
"0 2 * * *",
timeZoneInfo
);
recurringJobManager.AddOrUpdate(
"DispararEmailEmpresasInativas",
() => atualizadorBancos.DispararEmailDeAvisoEmpresasInativas(7),
"0 3 * * *",
timeZoneInfo
);
recurringJobManager.AddOrUpdate(
"DispararEmailAvisoCursos",
() => atualizadorBancos.DispararEmailDeAvisoCursosFuncionarios(),
"0 6 * * *",
timeZoneInfo
);
}
}
public static class StartupConfiguracoes
{
public static IServiceCollection AdicionarSwagger(this IServiceCollection services, IWebHostEnvironment hostEnvironment)
{
services.AddSwaggerGen(c =>
{
if (hostEnvironment.IsDevelopment())
c.SwaggerDoc("local", new OpenApiInfo() {Title = "Documentação Flow", Version = "local"});
c.SwaggerDoc("v1", new OpenApiInfo() {Title = "Documentação Flow", Version = "v1"});
c.SwaggerDoc("v2", new OpenApiInfo() {Title = "Documentação Flow", Version = "v2"});
c.IgnoreObsoleteActions();
c.IgnoreObsoleteProperties();
c.IncludeXmlComments(FilesComentsSwagger.API);
c.IncludeXmlComments(FilesComentsSwagger.Domain);
c.IncludeXmlComments(FilesComentsSwagger.SharedKernel);
c.OperationFilter<SwaggerImplementationNotesOperationFilter>();
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
In = ParameterLocation.Header,
Description = "Please insert JWT with Bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
BearerFormat = "Bearer {access_token}",
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
c.OperationFilter<AuthResponsesOperationFilter>();
c.DocInclusionPredicate((targetApiVersion, apiDesc) =>
{
return targetApiVersion == "local" || apiDesc.RelativePath.Contains(targetApiVersion);
});
});
return services;
}
public static IServiceCollection ConfigurarControllers(this IServiceCollection services)
{
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.Formatting = Formatting.None;
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None;
options.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
options.SerializerSettings.Converters = new JsonConverter[] {new ValorCampoPersonalizadoConverter()};
});
services.AddControllers();
return services;
}
public static IServiceCollection ConfigurarCors(this IServiceCollection services, string policy)
{
services.AddCors(o => o.AddPolicy(policy, builder =>
{
builder
.SetIsOriginAllowed(isOriginAllowed: _ => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.WithExposedHeaders("x-nome-arquivo", "x-notificacoes");
}));
return services;
}
public static IServiceCollection AdicionarHangfire(this IServiceCollection services, IConfiguration iconfiguration, IWebHostEnvironment hostEnvironment)
{
if (hostEnvironment.IsProduction())
{
services.AddHangfire(config => config
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(iconfiguration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
DisableGlobalLocks = true,
}));
}
else
{
services.AddHangfire(c =>
c.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseMemoryStorage());
}
services.AddHangfireServer();
return services;
}
public static IServiceCollection ConfigurarCompressaoResposta(this IServiceCollection services)
{
services.Configure<GzipCompressionProviderOptions>(x =>
{
x.Level = CompressionLevel.Fastest;
});
services.AddResponseCompression(x =>
{
x.Providers.Add<GzipCompressionProvider>();
});
services.AddResponseCaching();
return services;
}
public static IServiceCollection AdicionarAutenticacao(this IServiceCollection services, IConfiguration config, IWebHostEnvironment env)
{
var apiJwtToken = new JwtTokenConfig();
config.GetSection(nameof(JwtTokenConfig)).Bind(apiJwtToken);
services.AddSingleton(apiJwtToken);
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.TokenValidationParameters = apiJwtToken.TokenValidationParameters;
x.RequireHttpsMetadata = !env.IsDevelopment();
});
return services;
}
public static IServiceCollection AdicionarMailService(this IServiceCollection services, IConfiguration config)
{
var mailConfig = new MailConfig();
config.GetSection(nameof(MailConfig)).Bind(mailConfig);
services.AddSingleton(mailConfig);
services.AddSingleton<MailMessageSender>();
return services;
}
}
}
How I am sending the message: broadcast message
And how I am listening in Angular:
const options = {
accessTokenFactory: () => this.sharedService.token
};
let connection = new signalR.HubConnectionBuilder()
//.withUrl(environment.serviceBaseSignalR + "hubnotifications", options)
.withUrl("http://localhost:25580/hubnotifications", options)
.configureLogging(signalR.LogLevel.Information)
.build();
console.log(connection);
connection.on('broadcastMessage', function (name, message) {
console.log('message received:');
console.log(name);
console.log(message);
});
connection.connection.start().then(function () {
console.log('Now connected, connection ID=' + connection.id)
console.log(connection);
}).catch(function (err) {
return console.error(err.toString());
});

Is there anyone who can offer me a solution to this error? I can't understand where I'm wrong in configuring IdentityServer4

The image is here for Postman Request
It's a problem that I can't understand in the end, which is that it always gets a {"error": "invalid_request"}.
I request from Postman, using ImplicitFlow and OAuth 2.0, but I always get the same error.
I attach below the settings I made for IdentityServer4. I hope it will be useful to someone and can give me a solution.
public static class Config
{
public static IEnumerable<ApiResource> ApiResources()
{
return new[] {
new ApiResource("SkyEye.API", "SkyEye.API")
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId="ReactClient",
ClientName="SkyEye.SPA",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = new List<string>
{
"https://localhost:5002",
},
PostLogoutRedirectUris = new []{
"https://localhost:5002"
},
AllowedScopes= {
// IdentityServerConstants.StandardScopes.Profile,
// IdentityServerConstants.StandardScopes.OpenId,
"SkyEye.API"
}
}
};
}
public static IEnumerable<TestUser> Users()
{
return new[]
{
new TestUser
{ SubjectId = "1",
Username = "lascodaniil",
Password="password"
}
};
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddTestUsers(Config.Users().ToList())
.AddInMemoryClients(Config.GetClients())
.AddInMemoryApiResources(Config.ApiResources());
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseIdentityServer();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
I think your redirect URL is not valid. The URL in postman is different than the one provided in IDs configuration. The URL should be an exact match
Try changing this
RedirectUris = new List<string>
{
"https://localhost:5002",
},
to this
RedirectUris = new List<string>
{
"https://localhost:5002/connect/token",
},
Hope it helps