I have a controller method and usually check make Authorize like this:
[HttpGet]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Policy = "Policy")]
public async Task<IActionResult> Get(SomeType someObject)
{
return Ok();
}
Question: I need to Authorize based on RequireAuth field in someObject(passed in method), for example, if someObject.RequireAuth == true, then we need to apply
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Policy = "Policy")]
If someObject.RequireAuth == false, then [AllowAnonymous]
Related
A lot of my methods in my WebAPI looks like this. I have an authorization-filter on policy, but then i also need to validate my token and that the user provided in the token exists in DB. I am wondering if there is a way of extending the Authorize attribute so that after policy is validated, the token gets validated too. And is it also possible to return the user into the method or context if existing?
[Authorize(Policy = "Admin")]
[Route("get")]
[HttpGet]
public async Task<IActionResult> Get()
{
var (success, user) = await UserHelpers.ValidateTokenAndGetUserAsync(HttpContext, _userManager, _configuration, _logger, ControllerContext, ModelState);
if (!success || user?.CustomerId == null) return Unauthorized(HttpResponseHelper.GetErrorResponse(StatusCodes.Status401Unauthorized, _localizer[ResourceConstants.Unauthorized.UserNotFoundError].Value));
//Code
await UserHelpers.CookieToResponse(user, _configuration, _webHostEnvironment, Response, Request).ConfigureAwait(false);
return Ok();
}
First, I should address that you shouldn't customize Authorize attribute.
Using (Policy, Claim, Requirement) can help you to customize your authorization based on your business.
Then my question is that your user was in the database while issuing the token. So how can it be removed now? (The user in the JWT-Token is being validated via the signature of that)
If you want to double-check if the user is enabled, or not archived yet you can write another filter like this:
public class ApiController : Controller
{
[Authorize(Policy = "Bla")]
[CheckUserIsEnabled]
public async Task<IActionResult> Get()
{
return Ok();
}
}
public class CheckUserIsEnabledAttribute : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var userId = context.HttpContext.User.FindFirst(x => x.Type == System.Security.Claims.ClaimTypes.NameIdentifier);
if (userId is null)
{
context.Result = new UnauthorizedResult();
return;
}
bool exists = true;
//exists = db.CheckIfUserExistsAsync(userId);
if (!exists)
{
context.Result = new UnauthorizedResult();
return;
}
await next();
}
}
This solution works because the JWT claims will be added to current user claims on httpRequest instantiation (even if you don't put Authorize attribute on the corresponding action)
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 use CookieAuthentication in my application with owin and set redirect url on OnApplyRedirect like the following code:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
ExpireTimeSpan = TimeSpan.FromDays(30),
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/account/sign-in"),
//LogoutPath = new PathString("/account/log-out"),
ReturnUrlParameter = "returnTo",
CookieName = "BIR",
Provider = new CookieAuthenticationProvider()
{
OnValidateIdentity = SmObjectFactory.Container.GetInstance<IAppUserManager>().OnValidateIdentity(),
OnApplyRedirect = c =>
{
if (!c.Request.IsAjaxCall())
{
c.Response.Redirect(c.RedirectUri);
}
}
}
});
my problem is c.RedirectUri value, i set break point and trace my code after do this i understand that OnApplyRedirect called serveral time.
In the First call RedirectUri is:
http://localhost:7537/account/sign-in?returnTo=%2Fadmin-panel
In the Second call RedirectUri is:
http://localhost:7537/account/sign-in?returnTo=%2Faccount%2Fsign-in%3FreturnTo%3D%252Fadmin-panel
And more ...
In pre call old url add in new url.
I try to solve this problem, search and research in another and current site but do not find an answer, why OnApplyRedirect call several times?
Configuration method in Startup.cs class Only once is called.
other details :
Owin version: 3.1.0
ASP.NET MVC Version: 5.x
Visual Studio Version: 2017 (15.2)
Startup class in gist
Using the provided auth into, I was able to reproduce the problem by commenting out the [AllowAnonymous] on the SignIn action of a simple auto generated OWIN project.
So chances are that your situation is due to the sign in action requiring authentication while being used for anonymous access, thus causing an infinite loop of redirects which will fail.
In the following controller where authorization is needed to access its admin panel would cause the issue you are experiencing.
[Authorize]
[RoutePrefix("account")]
public class AccountController : Controller {
[Route("sign-in")]
public ActionResult Signin(string returnTo) {
ViewBag.ReturnTo = returnTo;
return View(new LoginViewModel { RememberMe = true });
}
[Route("admin-panel")]
public Action AdminPanel() {
return View();
}
}
All sign in, account verification and password recovery actions should be tagged with [AllowAnonymous] attribute to allow anonymous access if they are within [Authorize] controllers
[Authorize]
[RoutePrefix("account")]
public class AccountController : Controller {
[AllowAnonymous]
[Route("sign-in")]
public ActionResult Signin(string returnTo) {
ViewBag.ReturnTo= returnTo;
return View(new LoginViewModel { RememberMe = true });
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[Route("sign-in")]
public async Task<ActionResult> Signin(LoginViewModel model, string returnTo) {
//...
}
[Route("admin-panel")]
public Action AdminPanel() {
return View();
}
}
or should be moved to controllers that are not tagged with [Authorize] attribute.
[Authorize]
public class AccountController : Controller {
[Route("account/admin-panel")]
public Action AdminPanel() {
return View();
}
}
public class AuthenticationController : Controller {
[Route("account/sign-in")]
public ActionResult Signin(string returnTo) {
ViewBag.ReturnTo= returnTo;
return View(new LoginViewModel { RememberMe = true });
}
[HttpPost]
[ValidateAntiForgeryToken]
[Route("account/sign-in")]
public async Task<ActionResult> Signin(LoginViewModel model, string returnTo) {
//...
}
}
How can I disable an authorization filter for a specific GET handler in Web API?
There's a custom authorization filter on the class level but for one of the methods I need to have no security. I tried applying [AllowAnonymous] attribute but it still runs through the higher-level filter and fails. That custom filter derives from AuthorizationFilterAttribute. The class also have two another attributes: OverrideAuthentication and EnableCors.
I tried AllowAnonymous attribute but it doesn't.
Sample code:
[EnableCors(origins: "*", headers: "*", methods: "*")]
[OverrideAuthentication]
[AccountAuthorization]
public class AccountsController : ApiController
{
[Route("api/accounts/{accountNumber}/GetX")]
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage GetX(string accountNumber)
{
HttpResponseMessage response = null;
IEnumerable<string> apiKey;
if (!Request.Headers.TryGetValues("X-ApiKey", out apiKey) || apiKey.Count() != 1 || apiKey.First() != API_KEY)
{
throw new HttpResponseException(HttpStatusCode.Forbidden);
}
// Process
// ..
// ..
return response;
}
}
EDIT: The linked answer doesn't explain what's the solution.
Figured it out at last.
Since there is already an existing custom authorization filter on the class/controller level, therefore, to override a specific action handler (the method) and have it work without any authorization filters, we need to override the filter at the controller/class level. So adding the OverrideAuthorization filter did the trick. Now AllowAnonymous will be to do its magic.
[Route("api/accounts/{accountNumber}/GetX")]
[AllowAnonymous]
[OverrideAuthorization]
[HttpGet]
public HttpResponseMessage GetX(string accountNumber)
{
// Process
// ..
// ..
}
So my project requirements changed and now I think I need to build my own action filter.
So, this is my current login controller:
public class LoginController : Controller
{
// GET: Login
public ActionResult Index()
{
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model)
{
string userName = AuthenticateUser(model.UserName, model.Password);
if (!(String.IsNullOrEmpty(userName)))
{
Session["UserName"] = userName;
return View("~/Views/Home/Default.cshtml");
}
else
{
ModelState.AddModelError("", "Invalid Login");
return View("~/Views/Home/Login.cshtml");
}
}
public string AuthenticateUser(string username, string password)
{
if(password.Equals("123")
return "Super"
else
return null;
}
public ActionResult LogOff()
{
Session["UserName"] = null;
//AuthenticationManager.SignOut();
return View("~/Views/Home/Login.cshtml");
}
}
And this is my action filter attempt:
public class AuthorizationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (HttpContext.Current.Session["UserName"] != null)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary{{ "controller", "MainPage" },
{ "action", "Default" }
});
}
base.OnActionExecuting(filterContext);
}
}
I have already added it to FilterConfig, but when I login it does not load Default.cshtml it just keeps looping the action filter. The action result for it looks like this:
//this is located in the MainPage controller
[AuthorizationFilter]
public ActionResult Default()
{
return View("~/Views/Home/Default.cshtml");
}
So, what would I need to add in order to give authorization so only authenticated users can view the applicationĀ“s pages? Should I use Session variables or is there another/better way of doing this using? I am pretty much stuck with AuthenticateUser(), since what happens there now is just a simple comparison like the one we have there now.
Thank you for your time.
Create an AuthorizeAttribute with your logic in there:
public class AuthorizationFilter : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true))
{
// Don't check for authorization as AllowAnonymous filter is applied to the action or controller
return;
}
// Check for authorization
if (HttpContext.Current.Session["UserName"] == null)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
As long as you have the Login URL Configured in your Startup.Auth.cs file, it will handle the redirection to the login page for you. If you create a new MVC project it configures this for you:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(
new CookieAuthenticationOptions {
// YOUR LOGIN PATH
LoginPath = new PathString("/Account/Login")
}
);
}
}
Using this you can decorate your controllers with [AuthorizationFilter] and also [AllowAnonymous] attributes if you want to prevent the authorization from being checked for certain Controllers or Actions.
You might want to check this in different scenarios to ensure it provides tight enough security. ASP.NET MVC provides mechanisms that you can use out of the box for protecting your applications, I'd recommend using those if possible in any situation. I remember someone saying to me, if you're trying to do authentication/security for yourself, you're probably doing it wrong.
Since your attribute is added to the FilterConfig, it will apply to ALL actions. So when you navigate to your MainPage/Default action it will be applying the filter and redirecting you to your MainPage/Default action (and so on...).
You will either need to:
remove it from the FilterConfig and apply it to the appropriate actions / controllers
or add an extra check in the filter so that it doesn't redirect on certain routes