ASP.NET Core Authorization for owner only - asp.net

I have a question similar to Owner based Authorization
Is it possible to use resource-based authorization or policy-based authorization to allow only the owner of a model to view/edit/delete it?
With something like
[Authorize(Policy = "OwnerAuthorization")]
public class EditModel : PageModel
Or do I have to add logic to every OnGet/OnPost on every page for handling the authorization?

do I have to add logic to every OnGet/OnPost on every page for handling the authorization?
You don't have to, but for a better performance, you should add logic for every OnGet/OnPost on every page.
To authorize the request in the way of resource-based authorization, we need firstly to know what the resource is, and only after that we can authorize the user against the resource and policy.
Typically, we need load the resource from the server and then we can know whether the resource belongs to the current user. As loading the resource from server is usually done within action method, we usually authorize the request within the action method. That's exactly what is described in the official document.
var authorizationResult = await _authorizationService
.AuthorizeAsync(User, Document, "EditPolicy");
if (authorizationResult.Succeeded)
{
return Page();
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
However, if we choose to decorate the pagemodel with [Authorize(Policy = "OwnerAuthorization")] only, and don't invoke the _authZService.AuthorizeAsync(User, resource, "OwnerAuthorization"); within the action method, we'll have to load the resource within the authorization handler( or a simple function). In other words, we'll query the database twice. Let's say an user wants to edit a Foo model , and then make a HTTP GET request to /Foo/Edit?id=1 to show the form. The OnGetAsync(int? id) method is :
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null){ return NotFound(); }
// load foo from database
Foo = await _context.Foos.FirstOrDefaultAsync(m => m.Id == id);
if (Foo == null){ return NotFound(); }
return Page();
}
Now the resource-based authorization will load the Foo entity from database and check the owner. If succeeds, the action method will then validate the model.id and load resource from the database again.

Related

ASP.NET MVC changing post method to save userId

I'm new to ASP.Net MVC, learning it on my own.
I am trying to create a web-app to allow users to send requests online,
simply I've to Tables in my Database, using IdentityDbContext to generate userTable, and I got another Table 'Fatwa",
the user sends a request and saves them in Fatwa Table with UserID, to reference which user sends the request.
my problem is I got the POST request using scaffolding, it works fine, but I don't know how to Attach the userID with the Post request,
I did make it a ForeignKey in the database but don't know how to edit my post request to allow it to save the userID with the request.
please help me
thank you
this is the POST request
// POST: api/Fatwas
[HttpPost]
public async Task<IActionResult> PostFatwa([FromBody] Fatwa fatwa)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.Fatwa.Add(fatwa);
await _context.SaveChangesAsync();
return CreatedAtAction("GetFatwa", new { id = fatwa.FatwaID }, fatwa);
}
if i understand well you need the current user id, create object of user inside the class of Fatwa to make the relation between the user and fatwa then you can get the user from the current context HttpContext context inside the context there are context.User?.Identity.Name and much more

With same parameters and methods name, how can the controller finds which one to be invoked in ASP.NET core MVC

I am following the tutorial posted on the Microsoft website https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/details?view=aspnetcore-2.2
I just wonder once I click the delete button, how does it know which method or action should be invoked first? get or post? with the same parameters and action name
The code below might show you more details.
Thank you
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var movie = await _context.Movie.FindAsync(id);
_context.Movie.Remove(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
As stated in the comments above, a GET request will usually return a View to perform an action, but it won't actually perform that action unless it is a POST request, e.g. a GET request to an action named Edit will grab the data to edit and display it. That's it. The changes are not saved to the database until a POST to the Edit action is submitted.
Overloaded methods are required to have different parameter signatures. Since the other scaffolded pairs of CRUD actions (except Delete) have different signatures, they can have the same name. But since both the GET and POST methods for the Delete action have the same parameter signature, one of them needs to be renamed, which is why the POST action is named DeleteConfirmed. However, having GET and POST methods named differently will break the routing built into MVC. Adding the ActionName("Delete") attribute fixes that.
Routing depend on the HTTP Method + The name + The Parameters
so, when you issue a GET request to /Movies/Delete/5 it will use the first one.
When you issue a POST request to /Movies/Delete/5, it will use the second one.
If you have more than one POST method with different parameters, it will use the most specific. ex:
Delete(int id, bool confirm)
Delete(int id)
If you issue a POST request to /Movies/Delete/5, it will go for the second action, but if you change it to /Movies/Delete/5?confirm=true, it will go for the first one unless the parameter confirm was nullable, in this case it will throw an exception as it will not be able to determine which action to invoke

Dynamically adding steps to SSO with IdentityServer4

I`m facing some problems when trying to customize one of the quickstarts from identityServer4 QuickStart 9, basically, I need to create a single sign-on application that will be used by several services, multiple web applications, one electron, and PhoneGap app.
Currently, my flow is a bit more complicated than simply authenticating the user, see below:
User inputs login and password -> system validates this piece of data and presents the user with a selection of possible sub-applications to select -> the user selects one of the sub-applications -> the system now requests the user to select a possible environment for this application (staging/production can be customized)
I want to do this flow on the authentication layer because otherwise, I would have to replicate all these steps on all the apps, and off-course I want the authentication to have separate development lifecycle.
Currently, I'm trying to make 3 modifications to achieve this:
PersistentGrantStore -> save this steps to a custom table using the
grant key as a reference. (something like
Key/application/environment)
IProfileService -> add custom claims that represent this steps
(stuck here), and are temporary, they only have meaning for this token and subsequent refreshes.
authenticationHandler -> validate if the user went through all the
steps
I will also need to make a modification to the token endpoint to accept these 2 parameters via custom header due to my spa`s apps
my question boils down to: is there a better way to this? am I overcomplicating this?
sorry if this question is too basic, but I`m not used to doing this type of auth.
If i understand you correctly, following way might be helpful.
Create a temp cookie and display select page after user loggedin:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model, string button)
{
if (ModelState.IsValid)
{
var loginResult = .service.Check(model.Username, model.Password);
if (loginResult.IsSucceed)
{
await HttpContext.SignInAsync("TempCookies", loginResult.Principal);
var selectViewModel = new SelectViewModel();
model.ReturnUrl = model.ReturnUrl;
return View("SelectUserAndEnvironment", selectViewModel);
}
else
{
ModelState.AddModelError("", "****.");
return View(model);
}
}
return View(model);
}
Add claims you want and sign in for IdentityServerConstants.DefaultCookieAuthenticationScheme
[HttpPost]
[Authorize(AuthenticationSchemes = "TempCookies")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SelectUserAndEnvironment(SelectModel model)
{
// add claims from select input
var claims = new List<Claim>();
claims.Add(new Claim(<Type>, <Value>));
var p = new ClaimsPrincipal(new ClaimsIdentity(auth.Principal?.Identity, claims));
await HttpContext.SignOutAsync("TempCookies");
await HttpContext.SignInAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme, p);
return Redirect(model.ReturnUrl);
}
And use claims in ProfileService
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// you can get claims added in login action by using context.Subject.Claims
// other stuff
context.IssuedClaims = claims;
await Task.CompletedTask;
}
Finally add authentication scheme in Startup.cs
services.AddAuthentication()
.AddCookie("TempCookies", options =>
{
options.ExpireTimeSpan = new TimeSpan(0, 0, 300);
})
If you want to use external login, change above code appropriately.

ASP.NET Web API set custom status code

I have the following Api Controller:
[HttpPost]
public User Create(User user)
{
User user = _domain.CreateUser(user);
//set location header to /api/users/{id}
//set status code to 201
//return the created user
}
It seems like we have to depend on Request.CreateResponse(..) and change the signature of the controller so as to return IHttpActionResult.
I do not want to change the method signature as it is very useful for the documentation purpose. I am able to add the Location header using HttpContext.Current.Response... but not able to set the status code.
Anybody has any better idea on this?
Because you are using a custom (other) return type outside of void, HttpResponseMessage, and IHttpActionResult - it's harder to specify the status code. See Action Results in Web API 2.
From Exception Handling in Web API. If you want to stick with not modifying the return type then this might be something you can do to set the status code:
[HttpPost]
public User Create(User user)
{
User user = _domain.CreateUser(user);
//set location header to /api/users/{id}
//set status code to 201
if (user != null)
{
//return the created user
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Created, user);
}
else
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.InternalServerError));
}
}

ASP.NET Web API how to authenticate user

I'm trying to create a simple user authentication function but I just can't get it to work.
Here is the code I'm working on:
public class LoginController : ApiController
{
private void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
public bool Login(string token)
{
//Check token
if (.....)
{
//Authenticate user
var identity = new GenericIdentity("Test user");
SetPrincipal(new GenericPrincipal(identity, new string[]{"Test role"}));
}
}
[Authorize]
public string TestFun()
{
return "Hello " + User.Identity.Name;
}
}
So, if I try to call method TestFun() first, it returns error code 401 like it should.
However when I call method Login() it should somehow save user credentials, but this is where I get lost, I just can't get it to work.
TestFun() always returns error code 401 even if I call Login() first.
If I try to put return "Hello " + User.Identity.Name; in the Login() function it returns correct username, but in the TestFun() the user is not available.
I've even tried using Sessions and FormsAuthentication but I just can't get it to work, even on this really simple example.
Can someone please tell me what am I missing?
Thanks!
The Login method sets the principal for current request only. Just after the request completes, the principal context is wiped out so that the server can handle other requests for other users. When a new request comes, eons later from the server perspective, the principal context no longer exists and if nothing restores it, the request is unauthenticated.
To fix this you have to return something from your login method to the client. Not only bool but rather - an authentication token. Something the client could use to authenticate further requests.
It could be anything. Forms cookie would be fine as long as the client remembers to append it to further requests. Another common practice is to have a custom authentication token returned to the client and then appended by the client in a custom authentication header. And as forms cookies are handled by the Forms Authentication module, custom headers would need a custom mvc authentication filter or custom asp.net authentication module so that the token is readed, the identity is extracted and restored just before the request is about to execute.
If you don't like to bake your own token infrastructure, I would also recommend OAuth2 tokens. There is a great book that contains easy to follow examples on this and other possible authentication methods:
http://www.amazon.com/Pro-ASP-NET-Web-API-Security/dp/1430257822/ref=sr_1_1?ie=UTF8&sr=8-1&keywords=web+api+security
I just got the same issue, yes, I agreed we need to save that principal into somewhere (cookie, session) for other action to use, so, in SetPrincipal function I added
HttpContext.Current.Session["user"] = HttpContext.Current.User;
Now, the issue is how to get it back for other action, the idea popups in my mind is to extend AuthorizeAttribute and override IsAuthrized function, it will read the session first and if it found the session, it will return true, otherwise it will return false.
namespace BinZ
{
public class MyAuthorizeAttribute:AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext) {
HttpContext.Current.User = HttpContext.Current.Session["user"] as IPrincipal;
return HttpContext.Current.User != null;
}
}
}
Please remember to replace [Authorize] to [MyAuthorizeAttribute] in WebApi controller.
It works for me very well.
Cheers

Resources