Kestrel configuration to use a specific port + url - c#

I am using Asp.Net core 2.0.2 on Win 7 with VS2017 (15.3.5).
My current Kestrel configuration looks like this:
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration((hostContext, config) =>
{
var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
config.Sources.Clear();
config.AddJsonFile("appsettings.json", optional : false);
config.AddJsonFile($"appsettings.{envName}.json", optional : false);
config.AddEnvironmentVariables();
})
.UseKestrel(options =>
{
options.Listen(IPAddress.Loopback, 5859);
})
.UseContentRoot(pathToContentRoot)
.Build();
This obviously listens on http://localhost:5859. I want to configure Kestrel so that it ONLY listens on a custom URL such as http://localhost:5859/MyNewApp. How do I do that?
(With Core 1.0, I had used UseUrls("http://localhost:5859/MyNewApp") which partially did the job. It would listen on http://localhost:5859 as well as http://localhost:5859/MyNewApp. Doing the same in Core 2.0.2 results in an exception:
System.InvalidOperationException: A path base can only be configured using IApplicationBuilder.UsePathBase())

With 2.0 you need to leverage UsePathBase as UseUrls was removed from Kestrel. You'll want to do this in your Configure method at startup:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UsePathBase("/MyNewApp");
}

Related

SignalR JavaScript client error in connect to remote .Net Core Hub

I'm using Asp.Net Core (3) SignalR (Latest Version) as described in Microsoft's tutorial at here https://learn.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-3.1 but having error in connect to hub.
NuGet packages installed on server:
Microsoft.AspNetCore.SignalR(1.1.0)
Microsoft.AspNetCore.SignalR.Core(1.1.0)
My server runs at http://localhost:52852 and the client is running at http://localhost:10843.
I have added the client URL as acceptable Origin in server CORS policy.
Server Startup :
// ConfigureServices
services.AddCors(options =>
options.AddPolicy("CorsPolicy",
builder =>
{
builder
.AllowAnyMethod()
.AllowAnyHeader()
.WithOrigins("http://localhost:10843/")
.AllowCredentials();
}));
services.AddSignalR(hubOptions => {
hubOptions.EnableDetailedErrors = true;
});
// App Configure
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<AsteriskHub>("/signalr");
});
// Hub
public class MyHub : Hub
{
// Some Codes ...
}
Client Javascript :
const connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:52852/signalr")
.configureLogging(signalR.LogLevel.Information)
.withAutomaticReconnect()
.build();
connection.start();
I have read many documents similar to my issue over Microsoft and Asp.Net and Stackoverflow posts but confused why I have this error:
Access to XMLHttpRequest at
'http://localhost:52852/signalr/negotiate?negotiateVersion=1' from
origin 'http://localhost:10843' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
Thanks for any help.
.WithOrigins("http://localhost:10843/")
Please remove trailing slash / from the end of your URL, like below.
.WithOrigins("http://localhost:10843")
Besides, please apply your CORS policy with app.UseCors("CorsPolicy"), like below.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//... other middleware ...
app.UseCors("CorsPolicy");
app.UseRouting();
//...
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<AsteriskHub>("/signalr");
});
//...
}

ASP.Net Core 2.1 Vue.JS SPA - Cookie Auth and the Vue.JS Dev Server

I have an application which is essentially a Vue.JS SPA sat within a dotnet core 2.1 app, providing API services. When I start up the app, with my current Startup.cs configuration, it starts up 3 windows. 1 of these windows is the actual ASP.Net Core app root - the other 2 (don't ask me why 2) are copies of the Vue.js dev server that you get when you do npm run serve.
The problem that I have is that, whilst authentication is working fine if I use the instance running in the first window, if I try to log in using the Vue.JS server windows then I just get a 401 back.
My first thought was CORS, so I set up a CORS policy purely for when running in development mode but this hasn't rectified the issue. Developing without the Vue.JS server isn't really viable as without it I have no HMR and will need to reload the entire application state every time I make a design change.
I have had this setup working before with a .net Framework 4.6 API back end with no problem at all so I can only think it's a modern security enhancement that's blocking the proxying which I need to configure to be off in Development or something.
Any suggestions on how I can solve this are welcome.
Here's my current Startup.cs Configure...
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory,
ApplicationDbContext context, RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager)
{
if (env.IsDevelopment())
{
app
.UseDeveloperExceptionPage()
.UseDatabaseErrorPage()
.UseCors("DevelopmentPolicy");
}
else
{
app
.UseExceptionHandler("/Home/Error")
.UseHsts();
}
app
.UseAuthentication()
.UseHttpsRedirection()
.UseStaticFiles()
.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "VueApp";
if (env.IsDevelopment())
{
spa.UseVueCliServer("serve");
}
});
DbInitializer.Initialize(context, roleManager, userManager, env, loggerFactory);
}
...and ConfigureServices...
public void ConfigureServices(IServiceCollection services)
{
services
.AddLogging(builder => builder
.AddConsole()
.AddDebug());
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver());
services
.AddCors(options =>
{
options.AddPolicy("DevelopmentPolicy",
policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
});
// In production, the Vue files will be served from this directory
services.AddSpaStaticFiles(configuration => { configuration.RootPath = "wwwroot"; });
services.AddAuthentication();
services
.ConfigureEntityFramework(Configuration)
.ConfigureEntityServices()
.ConfigureIdentityDependencies()
.ConfigureDomainServices()
.ConfigureApplicationCookie(config =>
{
config.SlidingExpiration = true;
config.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = cxt =>
{
cxt.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnRedirectToAccessDenied = cxt =>
{
cxt.Response.StatusCode = 403;
return Task.CompletedTask;
},
OnRedirectToLogout = cxt => Task.CompletedTask
};
});
}
...and Identity gets lumped in with EF configuration here...
public static IServiceCollection ConfigureEntityFramework(this IServiceCollection services, string connectionString)
{
services
.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString))
.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = true;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
// User settings
options.User.RequireUniqueEmail = true;
})
.AddRoleManager<RoleManager<IdentityRole>>()
.AddSignInManager<SignInManager<ApplicationUser>>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
return services;
}
My vue.config.js looks like this...
const baseUrl = ''
module.exports = {
publicPath: baseUrl + '/',
// place our built files into the parent project where they will be copied
// into the distribution for deployment
outputDir: '../wwwroot',
filenameHashing: true, //process.env.NODE_ENV === 'production',
lintOnSave: 'error',
css: {
modules: false,
sourceMap: process.env.NODE_ENV !== 'production',
loaderOptions: {
sass: {
data: `
$fa-font-path: ${process.env.NODE_ENV !== 'production' ? '"~/fonts"' : '"' + baseUrl + '/fonts"'};
#import "#/scss/base/index.scss";
#import "#/scss/helpers/index.scss";
`
}
}
},
devServer: {
host: 'localhost',
port: 8080,
hot: true,
open: true,
openPage: '',
overlay: true,
disableHostCheck: true,
proxy: {
// Proxy services to a backend API
'/api': {
target: process.env.PROXY || 'https://localhost:44368',
secure: false,
changeOrigin: true
}
}
},
// these node_modules use es6 so need transpiling for IE
transpileDependencies: [
]
}
I solved this in the end by configuring the app so that it only forces HTTPS when not in development mode. HTTPS was killing the underlying Webpack Development Server and therefore Hot Module replacement - I can now debug successfully in the main window!
Happy to up vote better solutions than that though to get it working without killing SSL (if possible).

ArgumentNullException for ConnectionString When trying to remotely connect to .net core API

First of all is this happening in a Mac and I'm new to dotnet core.
I have installed dockers and setup everything in dotnet core. I did add connectionstring to the 'appsettings' and 'appsettings(Development)'.
"ConnectionStrings": {
"Default": "server=localhost; database=Monitor; User ID=sa; Password=MyComplexpPassword!234;"
},
This is Program.cs file Main method
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
This is startup.cs class ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<MonitorDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Default")));
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddScoped<IUserRepository,UserRepository>();
}
This is a Controller test method to test API.
[HttpGet("getUser")]
public UserResource GetUserInfo()
{
var user_1 = new User();
user_1.FirstName = "MAC";
user_1.LastName = "OS TEST";
user_1.Username = "Apple#gmail.com";
return mapper.Map<User, UserResource>(user_1);
}
This method will perfectly execute If I make a rest call(http) without setting up Program.cs class for remote access.
Now I have set it up to run in 'http://0.0.0.0:6001', So that I can access the API from my phone or from another pc in the same wifi.
I followed This instructions.
Now My Program.cs main method is like this.
public static void Main(string[] args)
{
// CreateWebHostBuilder(args).Build().Run();
var configuration = new ConfigurationBuilder()
.AddCommandLine(args)
.Build();
var hostUrl = configuration["hosturl"];
if (string.IsNullOrEmpty(hostUrl))
hostUrl = "http://0.0.0.0:6000";
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls(hostUrl) // <!-- this
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseConfiguration(configuration)
.Build();
host.Run();
}
In terminal I ran this command dotnet run --hosturl http://0.0.0.0:6001
If try to access same method as before this happens.
Notice:- I changed only the host, Because I need to test the API with other devices.
I have other controllers and methods that are connecting to the database and do crud operations with it, Those API calls also face the same issue like this. This only happens if I set it up to remote access.
Notice:- If I change the Startup.cs class Connection string line like this, It will work flawlessly in both configurations.
services.AddDbContext<MonitorDbContext>(options => options.UseSqlServer("server=localhost; database=Monitor; User ID=sa; Password=MyComplexpPassword!234;"));
But I felt that this is not good practice. In future, I have to add JWT Authentication to the API so that APP_Secret also needed to add to the AppSettings.json file.
Thank you.
you didn't tell the application to use appsettings.json. change below configuration
var configuration = new ConfigurationBuilder()
.AddCommandLine(args)
.Build();
To
var configuration = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddCommandLine(args)
.Build();
As an alternative, you can use the static WebHost.CreateDefaultBuilder method which by default loads settings from 'appsettings.json', 'appsettings.[EnvironmentName].json', and command line args.
Note -> As stated here:
AddCommandLine has already been called by CreateDefaultBuilder. If you
need to provide app configuration and still be able to override that
configuration with command-line arguments, call the app's additional
providers in ConfigureAppConfiguration and call AddCommandLine last.
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
// Call other providers here and call AddCommandLine last.
config.AddCommandLine(args);
})
.UseStartup<Startup>();

ASP.NET Core web service does not load appsettings.json into configuration

I have an ASP.NET Core 2.1 Web Application with Razor Pages which has AAD authentication information defined in the appsettings.json file (courtesy of the default application template - see below on how I got there). However, when trying to configure the authentication in Startup.cs the configuration does not have any of the config values from my appsettings.json. If I inspect the IConfiguration object in the debugger then it appears to only have the environment variable configurations:
Here's the Startup.ConfigureServices method where the issue lies:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options =>
{
// This is from the default template. It should work, but the relevant settings aren't there so options isn't populated.
this.Configuration.Bind("AzureAd", options);
// This of course works fine
options.Instance = "MyInstance";
options.Domain = "MyDomain";
options.TenantId = "MyTenantId";
options.ClientId = "MyClientId";
options.CallbackPath = "MyCallbackPath";
});
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
And the service configuration in case it's important (note that this is being built on top of a service fabric stateless service):
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[]
{
new ServiceInstanceListener(serviceContext =>
new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
{
ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");
return new WebHostBuilder()
.UseKestrel(opt =>
{
int port = serviceContext.CodePackageActivationContext.GetEndpoint("ServiceEndpoint").Port;
opt.Listen(IPAddress.IPv6Any, port, listenOptions =>
{
listenOptions.UseHttps(GetCertificateFromStore());
listenOptions.NoDelay = true;
});
})
.ConfigureServices(
services => services
.AddSingleton<StatelessServiceContext>(serviceContext))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
.UseUrls(url)
.Build();
}))
};
}
To create this service, I used the wizard in VS2017. I selected an existing service fabric project (.sfproj) and chose Services > Add > New Service Fabric Service and chose Stateless ASP.NET Core [for .NET Framework], then on the next page I chose Web Application (the one with Razor Pages, not MVC) and clicked Change Authentication where I chose Work or School Accounts and entered my AAD info. The only changes I have made to this template were adding the code inside the call to AddAzureAD in Startup.ConfigureServices and setting the appsettings.json files to always be copied to the output directory.
Why doesn't the appsettings.json file get loaded into the configuration? As I understand, this is supposed to happen by default, but something seems to be missing...
WebHostBuilder doesn't load appsettings.json by default, you need to manually call AddJsonFile. For example:
return new WebHostBuilder()
.UseKestrel(opt =>
{
//snip
})
.ConfigureAppConfiguration((builderContext, config) =>
{
config.AddJsonFile("appsettings.json", optional: false);
})
.ConfigureServices(
services => services
.AddSingleton<StatelessServiceContext>(serviceContext))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
.UseUrls(url)
.Build();
Alternatively you can use WebHost.CreateDefaultBuilder which will load more defaults.
Another approach, would be to manually create the configuration via ConfigurationBuilder then use the UseConfiguration method.
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false, true)
.Build();
var host = new WebHostBuilder()
.UseConfiguration(configuration)
.UseKestrel()
.UseStartup<Startup>();
The primary intent is core to provide a bit of flexibility when implementing, they often error on less is more. You have to explicitly say what you would like, that way the pipeline remains relatively small.
For others like me who find this issue:
It might be that you're not copying the appsettings.json file during build.
The OP does say he's doing this, but it's kind of a small print thing - and was what I was failing to do.
The more you know ...
Below mentioned steps worked for me
Go to Appsettings.json
Right click and goto properties
select the build action from the drop down to none if its content
Make the copy to Output directory as Copy Always
As mentioned before WebHostBuilder do not execute this default behavior CreateDefaultBuilder needs to be called instead.
I prefer following implementation:
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var logger = host.Services.GetRequiredService<ILogger<Program>>();
try
{
logger.LogInformation("Starting up");
host.Run();
}
catch (Exception ex)
{
logger.LogCritical(ex, "Application start-up failed");
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Make sure your file name is all lower case.
My mistake was to name the file appSettings.json instead of appsettings.json. When running within a Linux container, the camel-cased file was not loaded.

Aurelia Windows Authentication - Post 401 Unauthorized

I'm totally stuck on implementing Windows authentication for one of my .NET Core apps that uses Aurelia for client side.
The Aurelia application is hosted on port:9000 and the .NET WebAPI is hosted on port:9001.
The idea is to serve static pages from my .NET app once the app is published but now in development I use port:9000 because of the BrowserSync provided by Aurelia.
When I use port:9000 it's all fine and dandy and I have no issues posting or getting.
If I switch to port:9001 I can still get but not post. Posting results in 401 Unauthorized.
If we look at the headers for port:9000 requests..
Get(success):
Post(failed):
You can see that there are multiple headers missing in the post for some reasons, most importantly the authentication cookie..
Base-Repo.js
import {inject} from 'aurelia-framework';
import {HttpClient, json} from 'aurelia-fetch-client';
import {AppSettings} from '../infrastructure/app-settings';
#inject(HttpClient, AppSettings)
export class BaseRepo {
constructor(http, appSettings) {
http.configure(config => {
config
.withDefaults({
credentials: 'include',
headers: {
'Accept': 'application/json'
}
})
.withInterceptor({
request(request) {
console.log(`Requesting ${request.method} ${request.url}`);
return request;
},
response(response) {
console.log(`Received ${response.status} ${response.url}`);
return response;
}
})
});
this.http = http;
this.baseUrl = appSettings.api;
}
get(url) {
console.log('BaseRepo(get): ' + url);
return this.http.fetch(this.baseUrl + url)
.then(response => { return response.json(); })
.then(data => { return data; });
}
post(url, data) {
console.log('BaseRepo(post): ' + url, data);
return this.http.fetch(this.baseUrl + url, {
method: 'post',
body: json(data)
})
.then(response => response.json())
.then(data => { return data; });
}
}
Why is GET working but not POST when using BrowserSync port?
Edit 1
Post(success) for port:9001:
Edit 2
Console message post error:
OPTIONS http://localhost:9001/api/MYURLS 401 (Unauthorized)
Fetch API cannot load
http://localhost:9001/api/MYURLS.
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:9000' is therefore not allowed
access. The response had HTTP status code 401. If an opaque response
serves your needs, set the request's mode to 'no-cors' to fetch the
resource with CORS disabled.
Edit 3
Startup.cs
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
env.ConfigureNLog("nlog.config");
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddMemoryCache();
services.AddMvc();
services.InjectWebServices();
services.AddOptions();
//call this in case you need aspnet-user-authtype/aspnet-user-identity
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IConfiguration>(Configuration);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCors("CorsPolicy");
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMvc();
app.UseDefaultFiles();
app.UseStaticFiles();
//add NLog to ASP.NET Core
loggerFactory.AddNLog();
//add NLog.Web
app.AddNLogWeb();
}
}
I enabled "Enable Anonymous Authentication" in project properties and voila...
Before I only had "Enable Windows Authenticaiton" enabled, now both ports work!
When application is deployed this wont be enabled anyway because by then I will use the real IIS.
Update 1
After upgrading to .net core 2.0 I was no longer able to enable both Windows Authentication and Anonymous Authentication.
After some research I found out you have to add:
services.AddAuthentication(IISDefaults.AuthenticationScheme);
in your startup.cs in order for it to work.
More info can be found in comment section and docs.
Update 2
You need Microsoft.AspNetCore.Authentication package for authentication builder.
You will need to enable CORS in your ASP.NET Core project. There's information on how to do this here: https://learn.microsoft.com/en-us/aspnet/core/security/cors.
You need to call AddCors in ConfigureServices:
services.AddCors();
And then UseCors in Configure:
// Shows UseCors with CorsPolicyBuilder.
app.UseCors(builder => builder.WithOrigins("http://example.com"));
When you're using port 9000, you're on a different origin to the API, but with 9001, you're on the same origin and therefore CORS will not apply.
The OPTIONS requests are known as "preflighting". There's more information on those here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests.

Categories

Resources