Policy based authorization on razor pages - .net-core

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

Related

Why RequiredScope attribute doesn't have any effect?

According to this Microsoft document you should be able to apply attribute like [RequiredScope("SomeScopeName")] to either controller level or action level to protect the API. But when I try it in my API, it doesn't seem to have any effect at all - regardless what scope name I use (I made sure I don't have the scope by that name in the token), I always get right in to the API actions that I supposed to fail. But at the same time, my policy attributes, such as [Authorize(Policy = "PolicyName")], works just fine. What am I missing?
[ApiController]
[RequiredScope("AnyRandomName")]
public class MyApiController : ControllerBase
{
UPDATE
Here is my Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
services.AddControllers();
services.AddSwaggerGen(opt =>
{
opt.CustomSchemaIds(type => type.ToString() + type.GetHashCode());
});
services.Configure<HostOptions>(Configuration.GetSection(HostOptions.HOST));
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
services.AddAuthentication("Bearer").AddJwtBearer(options =>
{
options.Authority = Configuration[HostOptions.IDENTITYGATEWAY];
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
services.AddTransient<gRPCServiceHelper>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseExceptionHandler("/error-local-development");
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "GroupDemographicEFCore v1"));
}
else
{
app.UseExceptionHandler("/error");
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
and here is my API controller
[ApiController]
[Authorize]
[RequiredScope("NoSuchScope")]
public class MyApiController : ControllerBase
{
public MyApiController([NotNull] IConfiguration configuration, [NotNull] ILogger<MyApiController> logger,
[NotNull] gRPCServiceHelper helper) : base(configuration, logger, helper)
{
}
[HttpGet]
[Route("/clients/summary")]
public async Task<IActionResult> ClientsSummaryGet()
{
...
Note that I applied the attributes here on the controller level. But it makes no difference if I move them down to action level - the RequiredScope attributes always gets ignored.
UPDATE-1
I left out the AddAuthorization from my last post update, as I believe it is irrelevant to my issue here. I added it back now, with a few of the policies that I use. Once again, these policies are all working fine, and I don't see how this is relevant to the issue I have.
services.AddAuthorization(options =>
{
options.AddPolicy("OperatorCode", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("OperatorCode");
});
options.AddPolicy("OperatorCode:oprtr0", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("OperatorCode", "oprtr0");
});
options.AddPolicy("Role:User+OperatorCode:oprtr0", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("User");
policy.RequireClaim("OperatorCode", "oprtr0");
});
options.AddPolicy("Role:Admin||Role:User", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("Admin", "User");
});
});
Here is the access_token header
Here is the body of access_token
All we need to do is add
services.AddRequiredScopeAuthorization();
For the RequireScopeAttrubute to work, which is what AddMicrosoftIdentityWebApiAuthentication does under the hood to get it to work anyway.
What you need to do is to add and configure authorization in Startup.cs like, like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("ViewReports", policy =>
policy.RequireAuthenticatedUser()
.RequireRole("Finance")
.RequireRole("Management")
);
});
The policy says that the user must be authenticated and be in both roles. In this example RequireAuthenticatedUser() is optional.
Then you can use that policy like:
[Authorize(Policy = "ViewReports")]
public IActionResult ViewReports()
{
return View();
}
To get the role claim to work, you must define what the name of your role claim is in the token, by doing this:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
});
Otherwise the role might not be found, because OpenIDConnect and Microsoft have different opinion on what the claim should be called.
In the long run, using polices will gives you better and cleaner code, because if you need to change the scopes in the future, you need to update all controllers classes. With a policy , you change it in one place.
Also, according to this issue at GitHub, it says:
RequiredScopes just checks at the scp or
http://schemas.microsoft.com/identity/claims/scope claims.
This means that you might need to do some claims transformation (renaming) to get the RequiredScope to map to the scope claim in your access token.
My codes:
installing these 2 packages:
<PackageReference Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="4.5.1" />
<PackageReference Include="Microsoft.Identity.Web" Version="1.21.1" />
Startup.cs, adding code in ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd");
services.AddControllers();
}
don't forget these two lines in Configure method:
app.UseAuthentication();
app.UseAuthorization();
My test controller:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using System.Collections.Generic;
namespace WebApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
[RequiredScope("User.Read")]
public class HomeController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
[HttpPost]
public string getRes() {
return "hello world";
}
}
}
Test result :
==============================================================
To protect an ASP.NET or ASP.NET Core web API, you must add the
[Authorize] attribute to one of the following items:
The controller itself if you want all controller actions to be
protected The individual controller action for your API
According to this section's example,
adding [Authorize] before the line [RequiredScope("AnyRandomName")] ?
[ApiController]
[Authorize]
[RequiredScope("AnyRandomName")]
public class MyApiController : ControllerBase
{

How to inject service into custom ActionFilterAttribute (Web API)?

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

How are ASP.NET Roles used with Authorization?

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

Windows authentication with custom authorization requirement

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.

Where are plug-ins supposed to be added in ServiceStack

So simple yet I can't find any info or examples that explain exacty where this should happen. I'm guessing at this point that it should be in the Configure method.
Thank you,
Stephen
Global
public class AppHost : AppHostBase
{
public AppHost() : base("Web Services", typeof(ContactsService).Assembly) { }
public override void Configure(Container container)
{
//Set JSON web services to return idiomatic JSON camelCase properties
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
//Show StackTrace in Web Service Exceptions
SetConfig(new EndpointHostConfig { DebugMode = true });
//Register any dependencies you want injected into your services
container.Register<ICacheClient>(new MemoryCacheClient());
/* // Redis
container.Register<IRedisClientsManager>(c => new PooledRedisClientManager());
container.Register<IRepository>(c => new Repository(c.Resolve<IRedisClientsManager>()));*/
container.Register<IRepository>(new Repository());
container.Register<IBusinessService>(new BusinessService());
//Configure Custom User Defined REST Paths for your services
/*ConfigureServiceRoutes();*/
//Add a request filter to check if the user has a session initialized
/*this.RequestFilters.Add((httpReq, httpResp, requestDto) =>
{
var sessionId = httpReq.GetCookieValue("user-session");
if (sessionId == null)
{
httpResp.ReturnAuthRequired();
}
});*/
RequestFilters.Add((httpReq, httpResp, requestDto) => new LogRequestAttribute().Execute(httpReq, httpResp, requestDto));
Plugins.Add(new SwaggerFeature());
}
public static void Start()
{
new AppHost().Init();
}
}
Updated
public AppHost() : base("Web Services", typeof(ContactsService).Assembly) { }
public override void Configure(Container container)
{
....
ConfigurePlugins();
}
private void ConfigurePlugins()
{
Plugins.Add(new ProtoBufFormat());
Plugins.Add(new RequestLogsFeature());
Plugins.Add(new SwaggerFeature());
}
private void ConfigureServiceRoutes()
{
}
public static void Start()
{
new AppHost().Init();
}
There is no info because Plugins in ServiceStack can be added anywhere inside your AppHost.Configure() method. This is true of all ServiceStack configuration and registration of dependencies, services, filters, etc.
It doesn't matter where in the AppHost.Configure() method they're added because they're only Initialized by ServiceStack after it has been called.
They are however initialized (i.e. IPlugin.Register() is called) in the same order that they were added.

Resources