Is it possible to "disable" authentication in ASP.NET Core application without changing its logic?
I have a .net website which uses an external identity server app for authentication.
Anyway I would like to be able to mock the authentication when I'm developing it (ASPNETCORE_ENVIRONMENT = Development), airing access to all actions ignoring the authorization attributes.
Is it possible to do it just mocking some services in the service collection?
On updating to net core 3.1, the mvc AllowAnonymousFilter was not working for us any more. We found conditionally adding a custom IAuthorizationHander to be the simplest way forward to conditionally bypass auth.
eg.
/// <summary>
/// This authorisation handler will bypass all requirements
/// </summary>
public class AllowAnonymous : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList())
context.Succeed(requirement); //Simply pass all requirements
return Task.CompletedTask;
}
}
Then register this handler conditionally in Startup.ConfigureServices.
private readonly IWebHostEnvironment _env;
public Startup(IWebHostEnvironment env)
{
_env = env;
}
public void ConfigureServices(IServiceCollection services)
{
{...}
//Allows auth to be bypassed
if (_env.IsDevelopment())
services.AddSingleton<IAuthorizationHandler, AllowAnonymous>();
}
Note AddAuthentication and AddAuthorization services are still registered and configured as per prod code (which is nice).
To allow our unit test to bypass auth, we added a new anonymous testbase with a startup class that added this line without any conditions. Nice and simple!
You can bypass authorization in development environment by applying AllowAnonymousAttribute to your endpoints.
Example 1 dotnet new webapi template, .NET 6 (ASP.NET Core 6) and newer
Use AllowAnonymous method in Program.cs to apply AllowAnonymousAttribute to all controllers:
if (app.Environment.IsDevelopment())
app.MapControllers().AllowAnonymous();
else
app.MapControllers();
Example 2 dotnet new webapi template, .NET Core 3.0 - .NET 5 (ASP.NET Core 3.0-5)
Use WithMetadata method in Startup.Configure() to apply AllowAnonymousAttribute to all controllers:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseEndpoints(endpoints =>
{
if (env.IsDevelopment())
endpoints.MapControllers().WithMetadata(new AllowAnonymousAttribute());
else
endpoints.MapControllers();
});
}
Example 3 dotnet new webapi -minimal template, .NET 6 (ASP.NET Core 6) and newer
Use AllowAnonymous method to apply AllowAnonymousAttribute to a minimal API endpoint:
var hiEndpoint = app
.MapGet("/hi", () => "Hello!")
.RequireAuthorization();
if (app.Environment.IsDevelopment())
hiEndpoint.AllowAnonymous();
Details
endpoints and app from the examples above, both implement IEndpointRouteBuilder which has multiple Map extension methods like MapControllers() and MapGet(...) that return IEndpointConventionBuilder.
WithMetadata (available since .NET Core 3.0) and AllowAnonymous (available since .NET 5) are extensions for IEndpointConventionBuilder and can be called upon the results of those Map methods.
AllowAnonymousAttribute's description from the docs:
Specifies that the class or method that this attribute is applied to does not require authorization.
Another solution you may want to consider is using the IPolicyEvaluator. This means that you can keep all the existing security elements.
public class DisableAuthenticationPolicyEvaluator : IPolicyEvaluator
{
public async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
// Always pass authentication.
var authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(), new AuthenticationProperties(), JwtBearerDefaults.AuthenticationScheme);
return await Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
public async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
{
// Always pass authorization
return await Task.FromResult(PolicyAuthorizationResult.Success());
}
}
In the Startup.cs, ensure this appears at the top of the ConfigureServices method. Eg.
public void ConfigureServices(IServiceCollection services)
{
if (env.IsDevelopment())
{
// Disable authentication and authorization.
services.TryAddSingleton<IPolicyEvaluator, DisableAuthenticationPolicyEvaluator>();
}
...
Rather than Startup.cs (and thanks to the comments below) if you are using Core 3.1 and you wish to use the WebApplicationFactory, you can do the following:
public class MyWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
// Disable Authentication.
services.RemoveAll<IPolicyEvaluator>();
services.AddSingleton<IPolicyEvaluator, DisableAuthenticationPolicyEvaluator>();
});
}
}
I've found sollution for this problem on illucIT Blog.
This code must work:
if (env.IsDevelopment()) {
services.AddMvc(opts =>
{
opts.Filters.Add(new AllowAnonymousFilter());
});
} else {
services.AddMvc();
}
It's tricky to give a detailed answer without more details on your end, but I have previously achieved this by conditionally registering:
the external authentication middleware
the global policy that requires an authenticated request
it looked something like:
public class Startup
{
public Startup(IHostingEnvironment env)
{
Environment = env;
}
public IHostingEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(x =>
{
if (!Environment.IsDevelopment())
{
var authenticatedUserPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
x.Filters.Add(new AuthorizeFilter(authenticatedUserPolicy));
}
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseStaticFiles();
if (!Environment.IsDevelopment())
{
// Register external authentication middleware
}
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
In my case, the authorization filter was applied globally, so every single action of the MVC app required an authenticated user.
If you have different requirements - fine-grained [Authorize] attributes on some actions - then you could probably achieve the same result by changing how the associated authorization policies are built. They could basically contain no requirements at all.
AuthorizationPolicy yourCustomPolicy = null;
if (Environment.IsDevelopment())
{
yourCustomPolicy = new AuthorizationPolicyBuilder().Build();
}
else
{
yourCustomPolicy = new AuthorizationPolicyBuilder()
// chaining appropriate methods to suit your needs
.Build();
}
In ASP.NET Core 6, we managed to disable the authorization without changing any other part from the productive code, just the following logic in Program.cs:
if (!builder.Environment.IsDevelopment())
{
app.MapControllers();
}
else
{
app.MapControllers().AllowAnonymous();
}
This is to clarify #Kirill Lutsenko's answer about the method he found on the IllucIT blog post (note that in my case this is for .NET Core 2.0. I see other answers saying the AllowAnonymousFilter method won't work in .NET Core 3.1):
The Startup class has an overloaded constructor. One of the overloads takes an IHostingEnvironment parameter. You need to use this version of the constructor.
In the Startup class create a property of type IHostingEnvironment. Call it, say, Environment. Then set that property in the constructor.
Then, in the ConfigureServices method, you can use Environment.IsDevelopment().
public class Startup
{
public Startup(IHostingEnvironment environment)
{
Environment = environment;
}
public IHostingEnvironment Environment { get; }
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//...
services.AddMvc(options =>
{
// This uses the Environment property populated in the constructor.
if (Environment.IsDevelopment())
{
options.Filters.Add(new AllowAnonymousFilter());
}
// Set other options here. For example:
options.ModelBinderProviders.Insert(0, new UTCDateTimeModelBinderProvider());
//...
});
//...
}
}
As a side note, in real life we use a different overload of the constructor, which takes both an IConfiguration object and an IHostingEnvironment object as parameters. That allows us to configure services based on an appsettings.json configuration file.
For example:
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
public IConfiguration Configuration { get; }
public IHostingEnvironment Environment { get; }
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//...
// Data access via Entity Framework
services.AddDbContext<ContainersDbContext>(options =>
{
options.UseNpgsql(Configuration.GetConnectionString("OrdersDatabase"));
});
//...
}
}
Related
I am currently working on a project that consists of sub-projects such as WebApp, API, and Client class library. (The project structure is shown below).
Project Solution Structure
Although the project is a web-based project, it uses windows Identity as authentication identity since it is an internal application. I implemented the authorization policy of the WebApp project without any problems by following the steps in the implementation_link.
Now I can control access using DataAnnotation in WebApp (ex. [Authorize(Roles = "Admin"]). If I add Authorization control on the API side, WebApp cannot access this API. This is because of HttpContext.User is null. I found the solution to this problem solution_link. I adapted this solution to the project as below:
ServiceCollectionExtensions.cs in WebApp project:
public static IServiceCollection AddAuraServices(this IServiceCollection serviceCollection, IConfiguration configuration)
{
serviceCollection.AddTransient<IModelDatabaseNamesProvider, StandardCasingModelDatabasesNamesProvider>();
serviceCollection.Configure<RouteOptions>(routeOptions =>
{
routeOptions.ConstraintMap.Add(ModelDatabasesNameConstraint.Name, typeof(ModelDatabasesNameConstraint));
});
serviceCollection.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
serviceCollection.AddScoped<IModelMetadataProvider>(serviceProvider =>
{
var httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
var modelName = httpContext.Request.RouteValues["model-name"].ToString();
return new ModelMetadataProvider(modelName);
});
DateOnlyTypeConverter.AddAttributeToType();
serviceCollection.AddHttpClient<UploadRulesClient>("ServerAPI", (httpClient) =>
{
httpClient.BaseAddress = new Uri(configuration["AuraApiClient:BaseAddress"]);
}).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
serviceCollection.AddHttpClient<ScenarioZipFilesClient>("ServerAPI",(httpClient) =>
{
httpClient.BaseAddress = new Uri(configuration["AuraApiClient:BaseAddress"]);
}).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
serviceCollection.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("ServerAPI"));
var jsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
ClientJsonResponse.Configure(jsonSerializerOptions);
serviceCollection.AddSingleton(jsonSerializerOptions);
serviceCollection.AddAuraDropzoneConfig(configuration);
return serviceCollection;
}
Startup.cs of WebApp:
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(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
services.AddAuthorization();
services.AddControllersWithViews();
//services.AddRazorPages();
services.AddAuraServices(Configuration);
}
// 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();
}
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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "model-database", pattern: "{model-name:modeldatabasename}/{controller=ZipFiles}/{action=Index}/{id?}");
endpoints.MapControllerRoute(name: "default", pattern: "", new { controller = "Home", action = "Index" });
//endpoints.MapRazorPages();
});
}
}
But this time I am getting No service for Type Error. How can I solve this problem? Where do you think I am going wrong? Thanks
Edit:
As you can see BaseAddressAuthorizationMessageHandler is in namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication. It is supposed to be used with Blazor WebAssembly apps and it attaches the access token to the authentication header of HttpClient requests. BaseAddressAuthorizationMessageHandler depends on other services like IAccessTokenProvider which is responsible to return the access token. For example in web assembly IAccessTokenProvider default implementation retrieves the access token from browser session storage.
If you want to attach access tokens to your http requests your should probably implement your own DelegatingHandler instead of BaseAddressAuthorizationMessageHandler.
Old answer:
You have to register BaseAddressAuthorizationMessageHandler:
serviceCollection.AddTransient<BaseAddressAuthorizationMessageHandler>();
I am having CORS issue on app made in DOT NET FRAMEWORK 4.5.2 & Angular 6 on frontend
This is what I tried yet.
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
//EnableCorsAttribute cors = new EnableCorsAttribute("http://localhost:4200", "*", "GET,POST");
config.EnableCors();
// Web API routes
}
I tried with adding my localhost address as well. It didnot work.
The I added a CorsPolicy as well that didnt work too.
public class MyApiCorsPolicy : Attribute, ICorsPolicyProvider
{
private System.Web.Cors.CorsPolicy _policy;
public MyApiCorsPolicy()
{
// Create a CORS policy.
_policy = new System.Web.Cors.CorsPolicy
{
AllowAnyMethod = true,
AllowAnyHeader = true,
SupportsCredentials = true
};
}
public Task<System.Web.Cors.CorsPolicy> GetCorsPolicyAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
// Add the Request origin to the response.
_policy.Origins.Add(request.GetCorsRequestContext().Origin);
return Task.FromResult<System.Web.Cors.CorsPolicy>(_policy);
}
This above code is WebApiConfig.cs
This is StartUp.cs
public void Configuration(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); ConfigureAuth(app);
ConfigureAuth(app);
}
Image is also attached
There are two changes you'll need to make in your startup.cs file. The first is in the "ConfigureServices" method:
public void ConfigureServices(IServiceCollection services)
{
//.....
services.AddCors(options =>
{
options.AddPolicy("MyCorsSettings", builder =>
builder.WithOrigins(MySettings.CorsAllowedDomains.ToArray())
.AllowAnyHeader()
.WithExposedHeaders("Content-Disposition")
.AllowAnyMethod());
});
//.....
}
Note that in the above code MySettings.CorsAllowedDomains is just a string array of allowed domains (i.e. 'https://www.example.com') that is loaded from the appSettings.
The second change is in "Configure" and you need to pass in the same reference name (i.e. "MyCorsSettings")
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
....
app.UseCors("MyCorsSettings");
....
}
This is slightly changed for .net core 6. In program.cs add service
//Add cross-orgin resources sharing services
//Defined to use with dev and staging environment
var AllowAllOrigins = "AllowAllOrigins";
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins", policy =>
policy.WithOrigins("*")
.AllowAnyHeader()
.WithExposedHeaders("Content-Disposition")
.AllowAnyMethod());
});
//then add the middleware
app.UseCors(AllowAllOrigins);
I am trying to access the HttpContext.Session object in a helper class within my ASP.NET Core 2.1 project.
When I try to access HttpContext.Session I get the following error.
CS0120 An object reference is required for the non-static field, method, or property 'HttpContext.Session'
With .NET 4.x ASP.NET, it was easily accessed with "HttpContext.Current.Session".
Here is my class:
public class MySession
{
public void Foo()
{
HttpContext.Session.SetString("Name", "The Doctor"); // will not work
HttpContext.Session.SetInt32("Age", 773); // will not work
}
}
Here is my Startup.cs:
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.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = System.TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.Configure<ServiceSettings>(Configuration.GetSection("ServiceSettings"));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseSession();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
}
Do I need to inject something into the MySession class?
You can still access Session via the HttpContext. You how ever have to access the session via the IHttpContextAccessor, which as the name implies, will allow access to the HttpContext external to controllers and other framework classes that have it as a local member.
First you need to add the accessor to the DI container.
services.AddHttpContextAccessor();
API Reference
from there you need to inject it into the desired class and access the desired members
public class MySession {
IHttpContextAccessor accessor;
public MySession(IHttpContextAccessor accessor) {
this.accessor = accessor;
}
public void Foo() {
var httpContext = accessor.HttpContext;
httpContext.Session.SetString("Name", "The Doctor");
httpContext.Session.SetInt32("Age", 773);
}
}
We also need to initialise the session object
private ISession _session => _httpContextAccessor.HttpContext.Session;
Complete solution is below. Just in case anyone couldn't work out (like me) with above code.
Public class SomeOtherClass {
private readonly IHttpContextAccessor _httpContextAccessor;
private ISession _session => _httpContextAccessor.HttpContext.Session;
public SomeOtherClass(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void TestSet()
{
_session.SetString("Test", "Ben Rules!");
}
public void TestGet()
{
var message = _session.GetString("Test");
} }
Code taken from Using Sessions and HttpContext in ASP.NET Core and MVC Core
I have moved the api into a different folder structure then the usual offered by templates.
the structure looks like this
API
Controllers
LoginController.cs
LoginController has a basic method
[Route("api/[Login]")]
public class LoginController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Startup.cs
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddOptions();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
app.UseMvc();
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
The solution builds fine. when i try to access the page using following url it just sets
localhost is currently unable to handle this request.
HTTP ERROR 500
.
https://localhost:44352/api/login/get
https://localhost:44352/API/Controllers/login/get
Do some settings needs to be added to return the content.
You have no default route defined, which is fine, but then you're entirely reliant on each controller and action having attribute routes defined. On your LoginController, you do have a route attribute, but it's not correct. The brackets are for substituting certain route values like area, controller, etc.; it's not an indication that you actual controller name should go in there. In other words, you need either [Route("api/Login")] or [Route("api/[controller]")], where the latter will then be substituted with the controller name, Login, by ASP.NET Core.
Additionally, when using route attributes, action name no longer plays a part. If you don't define a route, it's the same as defining an empty route, i.e. [HttpGet("")]. Therefore, even with the fix to your controller route, the URL to that action would still be just /api/login, not /api/login/get. If you want the get, then you need to set the route to that: [HttpGet("get")].
I'm following Scott Allen's Asp.Net core Pluralsight course in Ubuntu 16.04 .Net Core 1.0.0 framework. I'm unable to find the app.UseRuntimeInfoPage method in Configure method in StartUp.cs file, even though I have included Microsoft.AspNetCore.Diagnostics. Does the framework have limitations for the non-windows operating systems in terms of the features provided?
StartUp.cs code from Scott Allen's course
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using OdeToFood.Services;
namespace OdeToFood
{
public class Startup
{
public Startup()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton(provider => Configuration);
services.AddSingleton<IGreeter, Greeter>();
}
// This method gets called by the runtime.
// Use this method to configure the HTTP request pipeline.
public void Configure(
IApplicationBuilder app,
IHostingEnvironment environment,
IGreeter greeter)
{
app.UseIISPlatformHandler();
if (environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRuntimeInfoPage("/info");
app.UseFileServer();
app.UseMvcWithDefaultRoute();
app.Run(async (context) =>
{
var greeting = greeter.GetGreeting();
await context.Response.WriteAsync(greeting);
});
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
}
This feature was removed some time ago.
https://github.com/aspnet/Home/issues/1632
Also, it seems like it is scheduled to come back at an undetermined moment in time.
https://github.com/aspnet/Diagnostics/issues/280
So for now you can remove it from your startup.cs; or add the code and create your own version of it from this commit:
https://github.com/aspnet/Diagnostics/commit/af19899927516718bdc05507612dcc17901fb937
I do not provide a code sample because the code is in the commit mentioned above.
UPDATE:
It seems like issue #280 has been updated to state that the feature will not be brought back at all.