Running Nancy Self Host under TopShelf - nancy

I wrote a Nancy Self-Hosted service using topShelf at work (windows 7) and it worked just fine. I brought it home and run it under Windows 10, and I get the following error:
The Nancy self host was unable to start, as no namespace reservation existed for the provided url(s).
Please either enable UrlReservations.CreateAutomatically on the HostConfiguration provided to
the NancyHost, or create the reservations manually with the (elevated) command(s):
netsh http add urlacl url="http://+:5000/" user="Everyone"
I saw this suggestion:
HostConfiguration hostConfigs = new HostConfiguration()
{
UrlReservations = new UrlReservations() { CreateAutomatically = true }
};
But it only seems to work when running your own host, not with TopShelf. Here's my Main code:
public static void Main()
{
HostFactory.Run(x =>
{
//x.UseLinuxIfAvailable();
x.Service<SelfHost>(s =>
{
s.ConstructUsing(name => new SelfHost());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
x.RunAsLocalSystem();
x.SetDescription("SciData Recipe Data Interaction");
x.SetDisplayName("SciData.Recipe Service");
x.SetServiceName("SciData.Recipe");
});
}
Can someone suggest how to fix this so it runs under Windows 10? Thanks...
UPDATE:
The following did work: running a command shell as admin and typing the following seems to make everything work.
netsh http add urlacl url=http://+:1234/ user=DOMAIN\username
Where 1234 is the port the service uses. I'd still like to find out how to do this in the code, but if that doesn't work, this will suffice.

Take a look to Topshelf.Nancy also available as a NuGet package. It's doing the URL Reservation (netsh http) for you when you install the service. It will also be deleted when the service is uninstalled.
Add Topshelf.Nancy to your project
Add "WithNancyEndpoint" to your service
Your code:
public static void Main()
{
HostFactory.Run(x =>
{
//x.UseLinuxIfAvailable();
x.Service<SelfHost>(s =>
{
s.ConstructUsing(name => new SelfHost());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
s.WithNancyEndpoint(x, c =>
{
c.AddHost(port: 1234);
c.CreateUrlReservationsOnInstall();
});
});
x.RunAsLocalSystem();
x.SetDescription("SciData Recipe Data Interaction");
x.SetDisplayName("SciData.Recipe Service");
x.SetServiceName("SciData.Recipe");
});
}

Related

404 error when calling endpoints from swagger

I went accross a lot of different SO posts, without finding any that solved my problem, and I feel I am only missing a small thing.
I recently renamed my .net solution and manually added an Angular app in my project following this advice, and the controllers won't work again. I searched for any reluctant data that would still have the old name, but I found nothing like that.
There is a small particularity in my project, I have, in my Controllers folder an Administrators folder and a Clients folder.
Admin controllers are setup like this :
[Controller]
[Route("api/admin/[controller]/[action]")]
public class CareController : Controller
{
// GET requests
[HttpGet]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> GetAll()
{
...
}
...
}
And my client controllers are setup like this :
[Controller]
[Route("api/client/[controller]/[action]")]
public class ClientCareCategoryController : Controller
{
// GET
[HttpGet]
public async Task<IActionResult> GetAll()
{
...
}
}
Please note I deleted the constructor, [ProducesResponseType()], fields and namespace for clarity reasons. The namespaces use the new solution name.
In my Program.cs, I setup my application like this :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSwaggerGen(config =>
{
config.SwaggerDoc("Backend", new OpenApiInfo { Title = "Backend", Version = "0.1"});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/Backend/swagger.json", "Backend");
});
}
else
{
app.UseHttpsRedirection();
}
app.MapControllers();
app.Run();
The Spa related config is the exact same as the config specified in the issue I linked above.
I also checked my launchsettings.json file, and I also changed the solution name with the new one.
What I tried
Being helped by previous SO posts, here is what I tried without success using routing middleware and MapControllerRoute
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "client",
pattern: "api/client/{controller}/{action}");
endpoints.MapControllerRoute(
name: "admin",
pattern: "api/admin/{controller}/{action}");
});
In addition to that, I also tried removing "client" from one of the controllers, but it didn't work either.
Thanks a lot for any help. Please tell me if you need any other information.
EDIT :
The issue comes from the SPA. When I launch the app when commenting the app.UseSpa(), I can call my endpoints. But I have no idea why do I have this issue atm.

SignalR Hub not receiving user from client WPF (.netcore 3.1)

I have a WPF client which connects successfully to a Hub, but I cannot pass the user of the client to the Hub.
My connection.User?.Identity?.Name in my class implementing from IUserIdProvider returns null.
For my WPF client I use this to connect against the Hub:
_connection = new HubConnectionBuilder()
.WithUrl(viewModel.Endpoint, opts =>
{
opts.Credentials = new NetworkCredential("user", "password", "domain");
opts.UseDefaultCredentials = true;
})
.Build();
I have then the following provider registered as singleton:
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
As I mentioned above, the connection.User?.Identity?.Name; is returning null.
I don't know what else I can do to pass the user name from my client (WPF) to my Hub.
By the way, my Startup.cs looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddLogging();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
services.AddSignalR(hubOptions =>
{
hubOptions.EnableDetailedErrors = true;
});
services.AddScoped<IBuildsService, BuildsService>();
}
// 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();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<SyncCodeHub>("/signalr");
});
}
Any help would be much appreciated.
EDIT:
I update the code with:
services.AddAuthentication(IISDefaults.AuthenticationScheme);
But the problem continues, the identity user (IUserIdProvider) is returning null when called from the WPF client. I'm running the API locally with IISExpress.
EDIT:
From Microsoft docs:
Windows Authentication is only supported by the browser client when using Microsoft Internet Explorer or Microsoft Edge.
So I'm wondering if this is even possible with an Desktop as a client. I assume it should work, so I'm wondering if I'm still missing a point or if this is a bug related to the Version of SignalR I#m using (3.1.3)
You need to configure your ASP.NET Core app to use Windows authentication by calling AddAuthentication in the ConfigureServices method of the Startup class:
services.AddAuthentication(IISDefaults.AuthenticationScheme);
You should also edit your launchSettings.json file according to the docs:
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
"applicationUrl": "http://localhost:52171/",
"sslPort": 44308
}
}

.NET Core 3 preview: Synchronous operations are disallowed

I have an Angular.js app that I am porting to .NET Core.
It was working fine in the previous version of .NET Core 3 preview; 3.2.
However, after upgrading to latest 3.3 some of the get requests are returning this error:
InvalidOperationException: Synchronous operations are disallowed. Call
WriteAsync or set AllowSynchronousIO to true instead.
I can't see why this is happening with only some requests and not others.
I believe that by default Angular.js does async: xhr.open(method, url, true);
Can anyone shed some light on this?
This problem is described here: https://github.com/aspnet/AspNetCore/issues/8302
The workaround for now is to manually set AllowSynchronous to true in startup.cs;
// Startup.ConfigureServices
services.Configure<IISServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
It's worth noting that if you host on kestrel directly then your Program.cs should have appropriate ConfigureKestrel call
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureKestrel((context, options) =>
{
options.AllowSynchronousIO = true;
})
you can disable it for a special method
var syncIOFeature = HttpContext.Features.Get<IHttpBodyControlFeature>();
if (syncIOFeature != null)
{
syncIOFeature.AllowSynchronousIO = true;
}
or disable in all application scope
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureKestrel((context, options) =>
{
options.AllowSynchronousIO = true;
})
or in service configure startup
services.Configure<IISServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
If you are using a CustomWebApplicationFactory like me, you can set the flag in its constructor, It makes my test direct from VS2019 works.
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class
{
public CustomWebApplicationFactory()
{
Server.AllowSynchronousIO = true;
}

Identity server 4: intercept 302 and replace it with 401

I've got an app which is hosting simultaneously Identity Server 4 and a client app (Vue) which uses a couple of rest services defined in an area for managing the site. The idea is that users associated with a specific role can access the client app and call the rest services for performing the actions.
Currently, my problem is that when the api return 302 when the user doesn't belong to the admin role. I'd like to change this to a 401, but I'm having some problems with it.
If this was a simple aspnet core app, then I'd simply pass a lambda to the OnRedirectToLogin property of the cookie handler that takes care of the request. Unfortunately, IS4 will only allow me to set a couple of basic settings of the cookie (expiration and sliding). The same docs say that I can override the cookie handler. So, I've tried doing the following:
services.AddIdentityServer()
... // other configurations
services.AddAuthentication(sharedOptions => {
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;//IdentityServerConstants.ExternalCookieAuthenticationScheme;
sharedOptions.DefaultChallengeScheme = IdentityServerConstants.SignoutScheme;
})
... //other external providers...
.AddCookie( CookieAuthenticationDefaults.AuthenticationScheme, options => {
options.Events = new CookieAuthenticationEvents {
OnRedirectToLogin = ctx => {
if (ctx.Request.Path.StartsWithSegments("/Admin", StringComparison.OrdinalIgnoreCase)) {
ctx.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
}
return Task.CompletedTask;
};
});
I expected to seem my handler being called whenever a request is redirected to the login page, but it never happens. Can anyone help?
Thanks
EDIT: just to add that I'm also using aspnet identity for managing the user accounts...
Posting the answer here in case anyone is interested...
After some digging, I've found that using identity means that you can't customize the cookie handler by doing what I was doing. Fortunately, the ConfigureAuthenticationEvent that can be configured by the ConfigureApplicationCookie extension method already does the right thing: if it detects that the current request is an AJAX call, it will return 401; if not, it will return 302. And here was the problem: the request made from the vue client wasn't being considered an AJAX request because it wasn't setting the X-Request-With header to XMLHttpRequest.
So, all it was required was to configure axios to set the header in all the calls:
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
I wrote a middleware sometime ago for this exact purpose and never looked back so if you don't find better solution, perhaps the solution can help you as well:
public class RedirectHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RedirectHandlingMiddleware> _logger;
public RedirectHandlingMiddleware(RequestDelegate next, ILogger<RedirectHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
await HandleRedirect(context, ex);
await _next(context);
}
private Task HandleRedirect(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/Admin", StringComparison.OrdinalIgnoreCase) && context.Response.StatusCode == 302)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
return Task.CompletedTask;
}
}
Just need to register in Startup.cs:
app.UseAuthentication();
app.UseMiddleware<RedirectHandlingMiddleware>();

Infinite authentication loop when using identityserver4 in asp.net core 2.0

I have an Identity Server using identityserver4 framework, its url is http://localhost:9000
My web application is asp.net core 2.0, its url is http://localhost:60002. This application will use the login page of Identity Server.
I want after logging in, the Identity Server will redirect to the application page (http://localhost:60002)
Here is the Startup.cs of client application
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
private string AuthorityUri => Configuration.GetValue<string>("UserManagement-Authority");
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = AuthorityUri; // "http://localhost:9000"
options.RequireHttpsMetadata = false;
options.ClientId = "customer.api";
options.ClientSecret = "testsecret";
options.ResponseType = "code id_token";
options.Scope.Add("customerprivatelinesvn.api");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
services.AddMvc();
}
// 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();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
Here is the loggin page on Identity Server
But there is an infinite loop that calls to http://localhost:9000/connect/authorize endpoint, and then it returns to http://localhost:60002/signin-oidc with "Bad Request - Request Too Long" as below.
When I look at the cookies, there ar lots of items ".AspNetCore.Correlation.OpenIdConnect.xxx"
Here is the log on Identiy Server. It said that Identiy.Application was successfully authenticated.
Does anyone know what this problem is? And how to resolve this? Thank you very much.
Best regards,
Kevin
I also had a login loop after copying the startup code from an existing .NET Core 2.2 project and reused it in a new .NET Core 3.1 project.
The problem here was, that the app.UseAuthentication() must be called before the new app.UseAuthorization();
https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#migrate-startupconfigure
Only in case someone is running into this issue too...
Adding default Identity in the client app would cause an infinite redirect loop.
In the client app, if you need to use UserManager, RoleManager.
Then use the below code.
services.AddIdentityCore<IdentityUser>()
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddSignInManager<SignInManager<IdentityUser>>()
.AddEntityFrameworkStores<ApplicationDbContext>();
In your client app, in Startup check if you have something like
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Remove that part and try again.
In my case, I was missing RedirectUri when initiating the Signin from the client. Problem solved by adding the RedirectUri as below.
public IActionResult SignIn()
{
return Challenge(new AuthenticationProperties() { RedirectUri = "/" }, "oidc" );
}
Well, you do have a very long request shown there in your Identity Server log - and the error says "Bad Request - request too long". I'd guess that the problem is that your request is too big :)
maximum length of HTTP GET request?
Have you tried posting rather than using a GET?
This issue was solved after I updated the latest nuget package of IdentityServer4 and .NET Core.

Resources