Let's say I create Admin Controller that includes all refers to administration pages.
In each controller i do validation that the user is admin and other validations,
Its look like my code repeats itself and i want to make local event on enter any route in admin controller and do these validations, I know i can declare one function that do these validations but again I must need to set her on any method in AdminController.
Thanks for helpers.
Example of admin controller method:
public IActionResult References()
{
var user = Systems.Users.GetUser(HttpContext);
if (Systems.Users.IsValid(user) &&
Systems.Users.IsPermission(user, Models.User.EUPermissions.Admin))
return View();
return View("Error404");
}
You can do the following in Startup.cs ConfigureServices() method:
services.AddAuthorization(options =>
{
options.AddPolicy("AdminAccess", policy => policy.RequireRole("Admin"));
});
Now in your controller, you can use the following:
[Authorize(Policy = "AdminAccess")]
public class AdministratorController : Controller
{
public IActionResult Index()
{
return View();
}
}
This way, you don't have to write code for checking access. .Net core promotes dependency injection and other solid principles and you don't have write methods which are doing more than one responsibility.
Related
I have been searching for similar solutions online but everything seems overcomplicating, currently, I have a UserController that I only want users that are logged in to access, my current solution involves using if statements however I was wondering if it's possible to use the [Authorize] attribute and apply it to methods or the entire controller perhaps?
public class UserController : ASessionController {
public UserController (IAmazonDynamoDB dynamoDbClient, DynamoDBContext dynamoDbContext) : base(dynamoDbClient, dynamoDbContext) {
}
// [Authorize]
public IActionResult Index () {
// check session variable
if(!UserIsLoggedIn()){ /*redirect to sign in*/ }
return View();
}
}
Perhaps I am not understanding if this is the purpose of the Authorize attribute? Thank you in advance.
You can use the Authorize attribute on endpoints and / or the Controller itself
It will force the User to be authenticated to again access to the decorated item.
In addition you can also restrict it to authenticated users with a given or multiple roles like in the example
[Authorize(Roles = "Administrator")]
public IActionResult Index()
{
...
}
[Authorize(Roles = "Administrator,Guest")]
public IActionResult NotAnIIndex()
{
...
}
But you should read the Microsoft Documentation tuturial
I'm new to ASP.NET Core and I'm still very uncomfortable. Anyways, I would like to know if this way is correct or if exists better solutions.
I'm checking on every page if a user is logged on. If not, I will redirect page to login page:
public IActionResult Index()
{
if (User.Identity.IsAuthenticated)
{
return View();
}
else
{
return Redirect("Identity/Account/Login");
}
}
I'm adding this in every single page.
Instead of adding User.Identity.IsAuthenticated (very un-DRY) you should check out DataAnnotations - [AllowAnonymous] and [Authorize]. You can decorate whole controllers or specific methods with these annotations to allow authentication for specific functionality.
[AllowAnonymous]
public IActionResult Index()
{
return View();
}
[Authorize]
public IActionResult OnlyAuthenticatedUsers()
{
return View();
}
Then you can add, in your Startup.cs, redirection rules if the user is not authenticated.
Check this out:
https://www.aspsnippets.com/Articles/Using-Authorize-and-AllowAnonymous-Data-Annotation-attributes-in-ASPNet-MVC.aspx
https://forums.asp.net/t/2146773.aspx?Multiple+AuthenticationSchemes+Not+redirecting+to+login+page+when+adding+AuthenticationSchemes
Use the Authorize action filter
Action filter executes before and after an action method executes.
Action filter attributes can be applied to an individual action method
or to a controller. When action filter applied to the controller then it
will be applied to all the action methods in that controller.
For your case
[Authorize]
public IActionResult Index()
{
return View();
}
I am unable to implement policy-based authorization in ASP .NET Core for an action on a razor page.
I read through this comprehensive document on authorization and used its examples as guidence.
Razor page action code:
[Authorize(Policy = "test")]
public async Task<IActionResult> OnGetCreateAsync(string id)
Code in service configuration:
_ = services.AddAuthorization(options => {
options.AddPolicy("test", policy =>
policy.RequireAssertion(context =>
false));
});
I expect that if I call the action or endpoint service, e.g.
GET /Account?handler=Create
then the request will be denied with a 403 status response because the "test" policy states that everyone is unauthorized. However, in actual practice, the action is successfully called.
Razor Pages doesn't support [Authorize] at the handler level. i.e. You can only authorise a page as a whole, on the PageModel itself, as noted in the docs:
Policies can not be applied at the Razor Page handler level, they must be applied to the Page.
If authorising the page as a whole isn't a workable solution, you might need to move your OnGetCreateAsync handler into a controller/action pair, which can be attributed with [Authorize] accordingly.
There's also a related GitHub issue in the docs for this:
The [Authorize] filter attribute has been supported since 2.0 in Razor Pages, but note that it works at the page model class level
If you need a better workaround, see akbar's answer and Jim Yabro's answer.
Another solution for it is to check authentication by if clause.like this:
if (!HttpContext.User.Identity.IsAuthenticated)
{
return Redirect("/Front/Index");
}
and you can check roles in it too by finding role:
var user = await _userManager.FindByEmailAsync(model.Email);
var roles = await _userManager.GetRolesAsync(user);
I would recommend following Razor Pages authorization conventions in ASP.NET Core that looks like this:
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
});
In your case with policy test it would look like this:
options.Conventions.AuthorizePage("/Account", "test");
Source:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/razor-pages-authorization?view=aspnetcore-5.0
Authorize attribute is supported but only on PageModel like this:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace PageFilter.Pages
{
[Authorize]
public class ModelWithAuthFilterModel : PageModel
{
public IActionResult OnGet() => Page();
}
}
Source:
https://learn.microsoft.com/en-us/aspnet/core/razor-pages/filter?view=aspnetcore-5.0#authorize-filter-attribute
Don't use the AuthorizeAttribute since it's unsupported.
Instead, once you have configured your policies in Startup.cs, you will be able to check those policies from within the page handlers.
Inject IAuthorizationService into your page model constructor
Call AuthorizeAsync() from within the handler.
Run a conditional check on the result's .Succeeded property.
If .Succeeded is false, return a Forbid() result.
This has nearly the same result as the [Authorize(Policy=...)] but executes later in the page lifecycle.
using Microsoft.AspNetCore.Authorization;
// ...
public class TestPageModel : PageModel {
readonly IAuthorizationService AuthorizationService;
public TestPageModel(IAuthorizationService authorizationService) {
AuthorizationService= authorizationService;
}
// Everyone can see this handler.
public void OnGet() { }
// Everyone can access this handler, but will be rejected after the check.
public async Task<IActionResult> OnPostAsync() {
// This is your policy you've defined in Startup.cs
var policyCheck = await AuthorizationService.AuthorizeAsync(User, "test");
// Check the result, and return a forbid result to the user if failed.
if (!policyCheck.Succeeded) {
return Forbid();
}
// ...
return Page(); // Or RedirectToPage etc
}
}
my workaround using Permission-based Authorization in ASP.NET Core :
[Authorize(Permissions.PageX.AddParameter)]
public async Task<IActionResult> OnPostAddParameterAsync(uint id, string key, string value)
{
if (!this.ArePermissionsValid()) return Forbid();
/// ...
}
public static class PageExtensions
{
public static bool ArePermissionsValid(this PageModel page)
{
try
{
var authorizeAttribute = new StackTrace().GetFrames().FirstOrDefault(x => x.GetMethod().Name.StartsWith("On")).GetMethod().GetCustomAttribute<AuthorizeAttribute>();
if (authorizeAttribute == null) return true;
var hasPermissions = page.User.Claims.Any(x => x.Type.Equals("permission") && x.Value.Equals(authorizeAttribute.Policy));
return hasPermissions;
}
catch (Exception e)
{
Log.Error($"${nameof(PageExtensions)}.{nameof(ArePermissionsValid)} | {e.Message}");
return false;
}
}
}
I have users that have one of those roles:
RoleA (Attribute: AuthorizeRoleA)
RoleB (Attribute: AuthorizeRoleB)
In my controller I want to say this:
Everyone that has role of type RoleA
can access all the methods in this controller
[AuthorizeRoleA]
public class HomeController : Controller
{
public ActionResult MethodOne()
{
return View();
}
public ActionResult MethodTwo()
{
return View();
}
//****** Make an exception ********
//So in this case, let RoleA here, but let RoleB too.
[AuthorizeRoleB]
public ActionResult MethodThree()
{
return View();
}
}
And I have another controller:
Everyone that has role of type RoleB
can access all the methods in this controller
Just RoleB! No one else.
[AuthorizeRoleB]
public class AnotherController : Controller
{
public ActionResult Index()
{
return View();
}
}
So, this should be similar with the Authorize attribute when is used to decorate the controller, and the AllowAnonymous when is used inside the same controller, but I don't know how to achieve this behavior with custom attributes(filters).
My goal is to create custom attributes, where I can say:
In AuthorizeRoleA will be included n-roles
and in AuthorizeRoleB will be included n-other roles.
But AuthorizeRoleA will have highest priority than the other attributes.
Note: Maybe this is a duplicate, but I didn't find anything similar to this question.
In my MVC application I have a few different roles: Admin, General User, etc., etc.
I know that I can apply security to my Controllers via the Authorize attribute:
[Authorize(Roles="Admin")]
public ActionResult Create()
{
return View();
}
But I also need to apply some security to the Views to not display certain sections of the View to certain roles:
#if( User.IsInRole("Admin") )
{
#Html.ActionLink("Create", "Create")
}
Is it better to do it the above way, or handle this sort of security in a ViewModel:
public ActionResult Index()
{
var model = new IndexViewModel();
model.CanCreate = User.IsInRole("Admin");
return View(model);
}
View:
#( Model.CanCreate )
{
#Html.ActionLink("Create", "Create")
}
Does the second method have any benefits compared to the first or is it just a preference thing?
The second way is more preferred, as your business logic will stay at model level.
In your example, business logic is very simple. However, imagine that requirements have changed and now not only Admins can create content, but also General Users that signed up more than 1 month ago. With business logic in view you'd have to update all your views.
One way I have done this before is creating an action filter that inherits from the AuthorizeAttribute. The filter can be called something like DisplayIfAuthorizedAttribute, and in addition to the standard AuthorizeAttribute properties, has a property called ViewNameIfNotAuthorized.
The attribute calls the base method to do authorization, and if it fails, returns the ViewNameIfNotAuthorized view. Otherwise, it allows the action method to proceed normally.
You would then render these partial views via action methods, and call the action methods through Html.RenderAction or Html.Action in your parent view. Those action methods would be decorated with the attribute.
You now have a standardized way to do this and no authorization code polluting the internals of your action methods.
This is what the filter would look like:
public class DisplayIfAuthorizedAttribute : System.Web.Mvc.AuthorizeAttribute
{
private string _ViewNameIfNotAuthorized;
public DisplayIfAuthorizedAttribute(string viewNameIfNotAuthorized = null)
{
_ViewNameIfNotAuthorized = viewNameIfNotAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool isAuthorized = base.AuthorizeCore(filterContext.HttpContext);
if (!isAuthorized)
{
filterContext.Result = GetFailedResult();
}
}
private ActionResult GetFailedResult()
{
if (!String.IsNullOrEmpty(_ViewNameIfNotAuthorized))
{
return new ViewResult { ViewName = _ViewNameIfNotAuthorized };
}
else
return new EmptyResult();
}
}
Your action method would be decorate as:
[DisplayIfAuthorized("EmptyView", Roles="Admin")]
public ViewResult CreateLink()
{
return View("CreateLink");
}
You may need both...
Note that the 2nd one alone would not be secure, a user might be able to construct the URL for the actionlink in the browsers addressbar. So you absolutely need the attribute for security.
The second one is more a matter of user-friendliness or UI design. Maybe you want the user to be able to click Create and then have a choice to login differently.
Check the authorization in your controller and prepare the Viewmodel for the view according to your role's rules.
The views are used to simply show data. So, imo, they don't have to do roles check etc..
So prepare the ViewModel with the data it should have and let the View only render it. (the boolean property you're using it's enough imo)