I'm trying to do ASP.NET Core 2 api with windows authentication. I need some unusual authorization requirements so I decided to create my own requirement for a policy.
public void ConfigureServices(IServiceCollection services)
{
(...)
services.AddAuthorization(options =>
{
options.AddPolicy("MyPolicy", policy => policy.AddRequirements(new MyRequirement())
);
});
}
My requirement:
public class MyRequirement: IAuthorizationRequirement
{
(...)
}
Handler for it:
public class MyHandler: AuthorizationHandler<MyRequirement>
{
public IService userService;
public MyHandler(IService service)
{
this.service = service;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
(...)
return Task.CompletedTask;
}
}
And obviously, Authorize attribute for my method:
[Authorize(Policy = "MyPolicy")]
public IEnumerable<string> GetAll()
{
(...)
}
But when I try to access such a method I get:
InvalidOperationException: No authenticationScheme was specified, and
there was no DefaultForbidScheme found.
I wasted a lot of time trying to fix it. Why is it happening and how can I get it working?
Everything happens locally, on IISExpress.
Related
I tried this answer: [https://stackoverflow.com/questions/18406506/custom-filter-attributes-inject-dependency][1] to implement ActionFilterAttribute (System.Web.Http.Filters) for Web API project (not MVC). But my custom attribute never called in controller. I would be grateful for any advice.
Custom attribute:
public class MyAttribute : FilterAttribute { }
Filter:
public class MyFilter : ActionFilterAttribute
{
private readonly IMyService _myService;
public MyFilter(IMyService myService)
{
_myService = myService;
}
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
//do some with actionContext
throw new Exception("You can`t go here");
}
}
Controller method:
[My] // Not called
[HttpPost]
[Route("/do-some")]
public async Task DoSome(string myString)
{
//do some
}
Register filter:
public partial class Startup
{
protected void ConfigureApi(IAppBuilder app, IContainer container)
{
var configuration = new HttpConfiguration();
//...
var serviceInstance = container.GetInstance<IMyService>();
configuration.Filters.Add(new MyFilter(serviceInstance));
}
}
Is something wrong here?
Almost everything is fine with the your code, but you should register your filter and service in another way.
In Asp Net Core WebAPI there several ways you can register your filter:
Globally - for all controllers, actions, and Razor Pages. More information in Microsoft documentation
For only one controller/method. More information in Microsoft documentation
Example of global registration:
services.AddControllers(options =>
{
options.Filters.Add(typeof(LoggerFilterAttribute));
});
Example of method registration in Controller:
I want notice - in this case you should use ServiceFilter - this helps DI resolve any dependecines for your filter.
[HttpGet]
[ServiceFilter(typeof(LoggerFilterAttribute))]
public IEnumerable<WeatherForecast> Get()
{
}
This is my simple example for this task:
My SimpleService
public interface ISimpleService
{
void Notify(string text);
}
public class SimpleService : ISimpleService
{
public void Notify(string text)
{
Console.WriteLine($"Notify from {nameof(SimpleService)}. {text}");
}
}
ActionFilterAttribute
public class LoggerFilterAttribute : ActionFilterAttribute
{
private readonly ISimpleService _simpleService;
public LoggerFilterAttribute(ISimpleService simpleService)
{
_simpleService = simpleService;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
_simpleService.Notify($"Method {nameof(OnActionExecuting)}");
}
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
_simpleService.Notify($"Method {nameof(OnActionExecutionAsync)}");
return base.OnActionExecutionAsync(context, next);
}
}
The main step - you should choose way of registration, because there is main difference between global registration and per controller/method in code.
If you want use this way of registration - you need only register global filter and this is enough. All magic will be do by WebAPI with DI registration.
services.AddControllers(options =>
{
options.Filters.Add(typeof(LoggerFilterAttribute));
});
If you want use registration per controller/method. You need to register your filter in DI. Because without it you will have Exception.
services.AddScoped<LoggerFilterAttribute>();
[HttpGet]
[ServiceFilter(typeof(LoggerFilterAttribute))]
public IEnumerable<WeatherForecast> Get()
{
}
The last step register my service
services.AddTransient<ISimpleService, SimpleService>();
Results
I'm using ASP.NET Core and hosting what is basically the default template with Windows Authentication enabled. I'm hosting this on a dedicated IIS server, and have verified the app is receiving correct information from AD and it correctly authenticates my session.
I feel like I'm trying to do something very simple. If the user is in the security group (from AD) "Admin" they are able to access a specific function. If they aren't in that group they do not get access.
I slapped on the [Authorize] attribute to the service
(in ConfigureServices)
services.AddAuthentication(IISDefaults.AuthenticationScheme);
(in Configure)
app.UseAuthorization();
(in service)
[Authorize]
public class SiteService
{
private readonly string _route;
private readonly HttpClient _httpClient;
public SiteService(HttpClient httpClient)
{
_httpClient = httpClient;
_route = httpClient.BaseAddress.AbsoluteUri;
}
public async Task<IEnumerable<Site>> GetSites()
{
}
}
I can see in the logs that accessing the service gives me Domain/User. I then looked up the MS Docs here: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-3.1
And slapped on [Authorize(Roles = "Admin"). That worked. I then switched "Admin" with "sldkfjslksdlfkj". Nothing changed...I can still access the service.
Why is the Roles="x" check not working? How can I enable a relatively simple check to AD for a Security Group?
You could write a custom Policy Authorization handlers to check all of the users' ADGroups and check if they contain the desired group name.
Refer to the following:
1.Create CheckADGroupRequirement(accept a parameter)
public class CheckADGroupRequirement : IAuthorizationRequirement
{
public string GroupName { get; private set; }
public CheckADGroupRequirement(string groupName)
{
GroupName = groupName;
}
}
2.Create CheckADGroupHandler
public class CheckADGroupHandler : AuthorizationHandler<CheckADGroupRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
CheckADGroupRequirement requirement)
{
//var isAuthorized = context.User.IsInRole(requirement.GroupName);
var groups = new List<string>();//save all your groups' name
var wi = (WindowsIdentity)context.User.Identity;
if (wi.Groups != null)
{
foreach (var group in wi.Groups)
{
try
{
groups.Add(group.Translate(typeof(NTAccount)).ToString());
}
catch (Exception e)
{
// ignored
}
}
if(groups.Contains(requirement.GroupName))//do the check
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
3.Register Handler in ConfigureServices
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.Requirements.Add(new CheckADGroupRequirement("DOMAIN\\Domain Admin")));//set your desired group name
//other policies
});
services.AddSingleton<IAuthorizationHandler, CheckADGroupHandler>();
4.Use on controller/service
[Authorize(Policy = "AdminOnly")]
public class SiteService
I trying to set up policy-based authorization on razor pages on Core2.1.
I have set up the policy and decorated the razor page with the authorize attribute. I cannot figure what am I doing wrong or if something else needs to be done, but I cannot get the page to authorize. It always gives me
No web page was found for the web address:
localhost/ADENETCore/Account/AccessDenied?ReturnUrl=%2FADENETCore%2FContact
Can you please point me in the right direction?
ConfigureServices:
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact", "AtLeast21"); // with policy
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddSessionStateTempDataProvider();
Configure:
app.UseAuthentication();
app.UseMvc();
Policy Requirement:
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; private set; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
Policy Handler:
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
Razor Page:
[Authorize(Policy = "AtLeast21")]
public class ContactModel : PageModel
It is redirecting to the Account/AccessDenied page
You need to add your authorization handlers as singletons.
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
For more info check: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-2.2
I'm trying to implement .NET Core 2.2/SignalR 1.1.0.
In startup:
public void ConfigureServices(IServiceCollection services)
services.AddSignalR();
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
It works smoothly when I apply a one-to-one example.
But I need an architectural change.
My example:
public class ChatHub : Hub
{
ResponseHandler ResponseHandler { get; set; }
public ChatHub()
{
IHubCallerClients hubCallerClients = this.Clients;
ResponseHandler = new ResponseHandler(hubCallerClients);
}
public async Task SendMessage(string user, string message)
{
IHubCallerClients hubCallerClients = this.Clients;
await ResponseHandler.R();
}
}
If I tried to get this.Clients in the constructor it is coming with null data. But if I try to take it in the method, it comes full as expected.
I should get IHubCallerClients in the contructor so that I can forward it to another Response context.
Thanks advance!
OK. I solved the problem by
public class RequestHandler : Hub
{
ResponseHandler ResponseHandler { get; set; }
public RequestHandler(IHubContext<RequestHandler> hubContext)
{
ResponseHandler = new ResponseHandler(hubContext);
}
public async Task SendMessage(string user, string message)
{
await ResponseHandler.R();
}
}
Due to the nature of .net core, context comes to constructor as dependency.
"services.AddSignalR();" we're sure to add it to Scope.
"IHubContext hubContext" In this way, we can collect the contructured object.
Hi I am trying to create a custom authorize filter that will allow me to authorize requests coming from localhost automatically (which will be used for my tests).
I found the following one for Asp.net however am having trouble porting it to asp.net core.
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Request.Url.IsLoopback)
{
// It was a local request => authorize the guy
return true;
}
return base.AuthorizeCore(httpContext);
}
}
How can I port this to asp.net core?
You can create a middleware in which you can authorize requests coming from localhost automatically.
public class MyAuthorize
{
private readonly RequestDelegate _next;
public MyAuthorize(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
// authorize request source here.
await _next(httpContext);
}
}
Then create an extension method
public static class CustomMiddleware
{
public static IApplicationBuilder UseMyAuthorize(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyAuthorize>();
}
}
and finally add it in startup Configure method.
app.UseMyAuthorize();
Asp.Net Core did not have IsLoopback property. Here is a work around for this
https://stackoverflow.com/a/41242493/2337983
You can also read more about Middleware here