I am building a .NET Core 3.1 Web Application using .NET Identity to secure some static files (these are documentation files generated automatically that I do not want general users to be able to see). The application also uses MVC to serve some administration pages.
Things appear to work OK (if a new visitor to the site tries to go to the static pages, they get redirected to the 'Access Denied' page). If one logs in (and has the 'Admin' role set in the AspNet database), one can then see these files.
However, after such a user logs out, one can still get to the protected static files (although one can no longer get to the Admin pages served using MVC). Here is how the system is set up in the Startup.cs file:
using CoreMVC.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using System.IO;
namespace CoreMVC
{
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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
/*
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
*/
// This gets the [Authorize(Roles = "Admin")] decoration working. N.B. reset RequireConfirmedAccount to false
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
// Try putting the fallback authorization policy here
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireRole("Admin")
.Build();
});
}
// 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.UseDatabaseErrorPage();
}
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();
// Keep this in (otherwise we lose access to required resources)
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// New mapping for static files - Seems OK to call UseStaticFiles twice!
// Note: moved this after UseAuthentication and UseAuthorization
// Try <host>/Documents/index.html
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(env.ContentRootPath, "Documents")),
RequestPath = "/Documents"
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
}
Note that the protection for the static files is provided by the fallback policy set up by the call to services.AddAuthorization in the ConfigureServices method, so this is distinct from the methods used to secure the pages served using MVC. I guess the problem is here somewhere.
I have tried deleting all Cookies using the browser dev tools but to no effect. Cookies on the system whilst logged in are:
.AspNetCore.Identity.Application
ARRAffinitySameSite
.AspNetCore.Antiforgery.9fXoN5jHCXs
ARRAffinity
After logging out, the .AspNetCore.Identity.Application cookie disappears. There do not seem to be any session or local variables stored.
Can anyone explain why this access is still being allowed and how to prevent it? As mentioned above, I suspect it is something to do with how the fallback policy is applied but I do not understand the details.
Update: On further investigation it seems that access to the static pages is only granted on the pages visited whilst logged in. This suggests that the server is maintaining some kind of list - an IIS issue maybe?
Further update: Thanks to the suggestion below, I now realise that this is due to browser caching, so not really an issue. I have now tried sticking
<meta http-equiv="Cache-Control" content="private, no-store" />
into the head section of the HTML pages and this seems to have worked.
Related
I’m brand new to Blazor and I’m trying to learn by converting an old website/web API project into a .Net 6 Blazor Server app where I plan to have both the UI and the API in the same application. I created a Controllers folder and added a controller called ApiController. I also set up Entity Framework and created my Entity classes for my SQL database tables. I’ve added the first HTTPGET route and tried hitting it through Postman to see if it will work. However, I keep getting a message that the page can not be found.
I thinking I need to add something to the Program.cs to let it know that I’m wanting to use APIs and Routing but, in my research, I’m not finding what I’m missing or what needs to be added. Most examples want to use a WASM project which probably has the API and Routing information built in.
This is the URL I'm trying to hit.
https://localhost:7168/api/usersadmin/GetAppNames
ApiController.cs
using Microsoft.AspNetCore.Mvc;
using UsersAdmin_AS.Data;
namespace UsersAdmin_AS.Controllers
{
[Route("api/UsersAdmin/[action]")]
[ApiController]
public class ApiController : ControllerBase
{
[HttpGet]
[Route("GetAppNames")]
public List<string> GetAppNames()
{
//logger.Info("GetAppNames");
List<string> listAppNames = new List<string>();
try
{
var dataManager = new DataManager();
listAppNames = dataManager.GetAppNames();
}
catch (Exception ex)
{
//logger.Error("ERROR: {0} | {1} | {2}", ex.Message, ex.StackTrace, ex.InnerException);
throw;
}
return listAppNames;
}
}
Program.cs
using UsersAdmin_AS.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/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.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Replace [Route("api/UsersAdmin/[action]")] with [Route("api/UsersAdmin/[controller]")]
and comment out [Route("GetAppNames")] in your controller.. Your swagger should show GetAppNames endpoint
I found this post and it fixed my issue.
https://stackoverflow.com/questions/70583469/host-web-api-in-blazor-server-application
This is the route I used at the top of the controller.
[Route("api/UsersAdmin/")]
I used this as my specific route.
[HttpGet]
[Route("GetAppNames")]
I added the builder.Services.AddControllers(), commented out the app.MapBlazorHub() and app.MapFallbackToPage("/_Host"). I then added the app.UseEndpoints() function.
Here is my updated Program.cs file.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using UsersAdmin_AS.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddControllers();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/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.MapBlazorHub();
//app.MapFallbackToPage("/_Host");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapRazorPages();
endpoints.MapFallbackToPage("/_Host");
});
app.Run();
I am new and am studying ASP.NET Core 6 MVC. I am stuck in this error when building a Startup class like in my tutorial.
This is my Program.cs class:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Startup.cs class:
using Microsoft.Extensions.FileProviders;
using System;
using System.Collections.Generic;
namespace HCMUE_ASPCore_Lab02
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IFileProvider>
(
new PhysicalFileProvider
(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")
)
);
services.AddMvc();
}
}
}
After reading many posts, I know that ASP.NET Core 6's Program and Startup have changed and my tutorial is old now. But I don't know how to update my Program and Startup to fit with ASP.NET Core 6.
Please help me. Any help would be appreciated.
Thank you for reading.
The error message is saying that your application is trying to create an instance of FileUploadController but it doesn't know how to create an instance of IFileProvider to pass into the constructor.
This is a typical dependency injection error, You can register it in this code:
builder.Services.AddSingleton<IFileProvider>(new PhysicalFileProvider
(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")
));
You can follow this Docs to learn about Dependency injection and other fundamentals in .Net 6.
If you are not comfortable with the new changes in .Net6, you can add the Startup class according to this article.
You want to ensure that both the static file middleware, and any services that inject your file provider, end up using the same instance.
var provider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")
);
builder.Services.AddSingleton<IFileProvider>(provider);
...
app.UseStaticFiles(
new StaticFileOptions{
FileProvider = provider
});
But if you don't really need a different file provider, you can instead inject IWebHostEnvironment into your controller and reuse the default .WebRootFileProvider.
After switching from .net core 2.2 to 3.0 and then 3.1 locally we switched to endpoint routing. I have the following routes :
app.UseEndpoints(endpoints =>
{
// Using this for asp.net core identity
endpoints.MapRazorPages();
// Mapping 2 routes, one private, one public but that we don't want localized so in both cases was simpler to create an area
endpoints.MapAreaControllerRoute("Back", "Back", "back/{controller=Home}/{action=Index}/{id?}");
endpoints.MapAreaControllerRoute("Photo", "Photo", "photo/{controller=Photo}/{action=Index}/{id?}");
// The default mapping for our front office, this works just fine in iis express
endpoints.MapControllerRoute("default", "{lang:lang}/{controller=Home}/{action=Index}/{id?}");
// Fallback is used mainly to redirect you from / to /defaultlanguage
endpoints.MapFallbackToController("WrongEndpoint","Home");
});
I've used the following blog's code to check if i had the same endpoints in both environments : https://dev.to/buhakmeh/endpoint-debugging-in-asp-net-core-3-applications-2b45
In both cases i have the expected behavior (i tested it by putting that controller in an area which works) : the route i expect to take "default" is in priority 3 while the fallback is 2147483647 so it's clearly being skipped over.
Typing the full name to ignore optional components like mydomain/fr/Home/Index still redirects me to the fallback controller under IIS.
I have no idea why it would behave differently under IIS than IIS Express. Also note that both razor pages and the area controllers works just fine, it's only the default route that fails and falls back to well, the fallback.
Removing the fallback doesn't make the default controller work either.
EDIT 1: as requested in comments the full startup.cs bellow
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using FranceMontgolfieres.Models;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Routing;
namespace FranceMontgolfieres
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
Utilities.ConfigurationUtils.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.AddSingleton<IConfiguration>(Configuration);
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
.AddDbContext<FMContext>(options => options
.UseLazyLoadingProxies(true)
.EnableSensitiveDataLogging()
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services
.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<FMContext>();
services
.AddMemoryCache();
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = Configuration.GetConnectionString("SessionConnection");
options.SchemaName = "dbo";
options.TableName = "SessionCache";
});
services.AddHttpContextAccessor();
services
.AddSession(options => options.IdleTimeout = TimeSpan.FromMinutes(30));
services
.AddControllersWithViews()
.AddRazorRuntimeCompilation();
services.AddRazorPages();
services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("lang", typeof(LanguageRouteConstraint));
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Called by asp.net core by convention")]
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
// TODO CRITICAL Remove before production
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
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.
// TODO CRITICAL ENABLE BEFORE PRODUCTION
// app.UseHsts();
}
//app.UseHttpsRedirection();
app.UseRouting();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapAreaControllerRoute("Back", "Back", "back/{controller=Home}/{action=Index}/{id?}");
endpoints.MapAreaControllerRoute("Photo", "Photo", "photo/{controller=Photo}/{action=Index}/{id?}");
endpoints.MapControllerRoute("default", "{lang:lang}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapFallbackToController("WrongEndpoint","Home");
});
}
}
}
EDIT 2 : Added web config on deployed server following comment by Lex Li
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\PROJECTNAMEEDITEDOUT.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
</system.webServer>
</location>
</configuration>
<!--ProjectGuid: THERE IS A GUID HERE I REMOVED-->
Let me explain the situation I'm running into as it seems I'm the first one in the whole internet to deal with it and I ran out of ideas. I have a pretty simple Razor Pages website that is running on asp.net core 3.0 It is hosted on the Azure App Service. I am using the most standard Idenity Framework Provided By Microsoft. Here is my Startup.cs:
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using IIHF_Toolbox.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace IIHF_Toolbox
{
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.AddSession();
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.Lax;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"), sOptions =>
{
sOptions.EnableRetryOnFailure(20);
}), ServiceLifetime.Transient, ServiceLifetime.Transient);
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddCors(options =>
{
options.AddPolicy("AllowAllHeaders",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// services.AddControllers()
//.AddNewtonsoftJson();
services.AddRazorPages().AddNewtonsoftJson();
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSession();
app.UseAuthentication();
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("MYAPIKEY");
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.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
//app.UseMvc();
}
}
}
I believe it looks pretty standart.As you can see, I use the simplest login-password Sign-In mechanism with default IdentityUser. Now about the problem:
I login into my website with User-A, using it's credentials. Then, I open my website on a brand clean device that never previously visited the websites, nor logged into it, and I find out that somehow, that I'm signed in to the User-A without entering a password on a device that never visited this website before. This is more that a security bteach. This is basically the absence of security. And you've gussed it right - If I provide you with a link, and you follow it, you will basically enter website already logged into a User-A account.
N.B. The link to the website could be any - it is not something like if you follow "website.com/user-a" you'll get to his page with edit rights. The thing is that SignInManager.IsUserSignedIn(User) and User.Identity.IsAuthenticated both returns true. Maybe I'm misunderstanding that? But how in the world would brand new Session be injected with the same user as the last logged in one? I'm really running out of ideas. Any idea on what to check would be highly appreciated. Thanks.
Let's say we take a default asp netcore 2.2 application generated from one of the default VS templates.
After running dotnet publish --Release we get a folder containing the app binaries.
Running dotnet MyDemo.dll stars the app on default http://localhost:5000.
How do I go about changing the default port and host?
I've tried setting ASPNETCORE_URLS environment variable with no effect.
Some additional context: I know for local development we can setup different profiles in launchSettings.json and we can use dotnet run command to select which profile to run. However, after publishing there's no launchSettings.json and running the binary directly using dotnet MyDemo.dll doesn't seem to allow any additional configuration.
Please see below the Startup class.
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.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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// 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");
// 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.UseStaticFiles();
app.UseCookiePolicy();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseMvc();
}
}
You should be having Program.cs where your Kestrel webserver is configured. In those configurations it should be possible to specify url for hostname and port. Check something like this:
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://localhost:60000", "http://localhost:60001")
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
You can find other methods to configure endpoints in docs
Btw, for ContinuosIntegration/ContinuosDelivery having endpoint settings in json would be better choise, so maybe you can look into improving your CI/CD pipeline.
Also there it should be possible to specify url in the command line using something like this