ASP.NET Web API set custom status code - asp.net

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));
}
}

Related

Inform the user of unauthorized method call

I am currently developing a web application using SignalR Core. I am using an AuthorizeAttribute (without roles) to ensure only authenticated users will connect to our Hub.
For certain methods a specific policy is required. These methods are also decorated with an AuthorizeAttribute containing a specific policy. Here is my Hub code:
[Authorize]
public class CustomerHub : Hub
{
public async Task SimpleMethod()
{
// Do things that don't require additional authorization
await Task.CompletedTask;
}
[Authorize(Policies.IsAdmin)]
public async Task AdvancedMethod()
{
// Do things that require admin authorization
await Task.CompletedTask;
}
public async Task ErrorMethod()
{
await Task.CompletedTask;
throw new NotImplementedException();
}
}
It all works as intended. I am able to connect to the Hub and call the SimpleMethod and the debugger does not step into the AdvancedMethod when I call it with insufficient rights. However when a user is not authorized to call a specific method I would like to inform them about this. But no specific error or message is sent back to the client in this case. I did implement a custom IHubFilter which informs the user of errors, but it turns out the AuthorizeAttribute is evaluated before this HubFilter is invoked.
public class CustomHubExceptionsFilter : IHubFilter
{
public async ValueTask<object?> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object?>> next)
{
try
{
var result = await next(invocationContext);
return result;
}
catch (Exception ex)
{
await invocationContext.Hub.Clients.Caller.SendCoreAsync("Error", new object?[] { $"Oops, something went wrong. Technical details: {ex.GetType().Name}" });
throw;
}
}
}
I also tried adding a custom AuthorizationHandler which returns an error when a user is unauthorized. However this causes the connection to be closed which is not a great solution in my opinion.
Is there a away to inform the user using AuthorizeAttributes? I could create a custom IHubFilter which checks whether the user is authorized. But that would require a lot of custom code. Is there a simpler/more native method available?
I suggest you could consider calling the Hub method's ErrorMethod directly inside the custom AuthorizationHandler inside of directly return the exception.
For example:
We need store the connection id inside the request, then inside the AuthorizationHandler we could get it and then use hub method to return the error to the client.
Like this :
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ConnectionRequirement requirement)
{
var connectionIdClaim = context.User.FindFirst("connectionId");
if (connectionIdClaim == null)
{
context.Fail();
return;
}
var connectionId = connectionIdClaim.Value;
await _hubContext.Clients.Client(connectionId).SendAsync("message", "You are not authorized to perform this action.");
context.Fail();
}

ASP.NET Core Authorization for owner only

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.

Questions about implementing an API on asp.net webapi and HTTP verbs

I'm creating api using ASP.NET Web API
I have a method in repository which adds worker to company.
Here is the method:
public void AddToCompanyBy(Guid workerId, Guid companyId)
{
var worker = GetById(workerId);
var company = DbContext.Set<Company>().Find(companyId);
if (worker == null)
throw new Exception("This worker does not exist");
if (company == null)
throw new Exception("This company does not exist");
company.Workers.Add(worker);
}
And I have an ApiController action that invokes this method.
Here is this action:
public IHttpActionResult AddToCompany(Guid workerId, Guid companyId)
{
try
{
UoW.Workers.AddToCompanyBy(workerId, companyId);
return Ok();
}
catch (Exception ex)
{
return BadRequest();
}
}
So my questions are:
am I right returning response as OK or I have to choose another type for response?
Should I also return entity?
How do I have mark the action (PUT or PATCH or GET)?
am I right returnign response as OK or I have to choose another type for response
It is ok to return OK response. You shouldn't specify return type because of Content Negotiation in Web API.
Should I also return entity?
It depends on your API and what do consumers of your API expect to get as a result. If it is enough just to know that everything was correct you can leave just OK message. If they need some data that was generated by your server, for exmaple incremented ID, you should return this data or even full entity.
How do I have mark the action (PUT or PATCH or GET)?
[HttpPost]
public IHttpActionResult AddToCompany(Guid workerId, Guid companyId)
[HttpGet]
public IHttpActionResult AddToCompany(Guid workerId, Guid companyId)
UPDATED according to comment:
I mean which verb should I choose in this particular case?
Both PUT and POST can be used for creating. For sure you shouldn't use GET verb for creation or update.
General Http verbs specification: w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3
Post with this discussion: stackoverflow.com/questions/630453/put-vs-post-in-rest

How to return a custom view OR a JSON when the Authorization fail, instead of showing a username and password dialog

I am working on an asp.net mvc 4 web application , and i wrote the following custom authorization class:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : AuthorizeAttribute
{
public string Model { get; set; }
public string Action { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAuthenticated)
return false;
//code goes here
if (!repository.can(ADusername, Model, value)) // implement this method based on your tables and logic
{
return false;
//base.HandleUnauthorizedRequest(filterContext);
}
return true;
// base.OnAuthorization(filterContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewResult = new JsonResult();
viewResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
viewResult.Data = (new { IsSuccess = "Unauthorized", description = "Sorry, you do not have the required permission to perform this action." });
filterContext.Result = viewResult;
}
else
{
var viewResult = new ViewResult();
viewResult.ViewName = "~/Views/Errors/_Unauthorized.cshtml";
filterContext.Result = viewResult;
}
base.HandleUnauthorizedRequest(filterContext);
}
}
}
but the only problem i am facing now is that if the authorization fail then the user will be prompted to enter username and password, although i have override the HandleUnauthorizedRequest to return a view or JSON based on if the request is AJAX or not. so can you advice why the user is being prompted to enter his username and password when the authorization fail, instead of receiving the _unauthorized view or the JSON containing an error message
but the only problem i am facing now is that if the authorization fail
then the user will be prompted to enter username and password,
although i have override the HandleUnauthorizedRequest to return a
view or JSON based on if the request is AJAX or not.
That's because you are absolutely always hitting the following line in your HandleUnauthorizedRequest method:
base.HandleUnauthorizedRequest(filterContext);
You know what this line do? It calls the base method. You know what the base method do? It returns 401 status code. You know what happens when 401 status response code is returned in an ASP.NET application in which you are using Forms Authentication? You get the login page.
So yeah, if you are using AJAX or something and intend to be returning some JSON or something make sure that the base stuff is never called. By the way in your else condition you seem to be attempting to render some ~/Views/Errors/_Unauthorized.cshtml view which obviously is useless once again because you are also calling the base method which will simply redirect to the login page.
I think that at this stage of my answer you already know what to do: get rid of this last line of your HandleUnauthorizedRequest method in which you are throwing all your efforts into the trash by calling the base method.
And if you want to do things properly and return 401 status code and not get the login page but instead return some custom JSON you could use the SuppressFormsAuthenticationRedirect property on the Response object. And if you are using some legacy version of the .NET framework which doesn't have this property you might find the following blog post useful in which Phil Haack explains how to handle this case.

About Response.Redirect, FormsAuthentication and MVC

I want to understand the difference in the behavior of a program when we call FormsAuthentication.RedirectFromLoginPage vs. when we call Response.Redirect(FormsAuthentication.GetRedirectUrl()) and manually redirect.
Please see the comments below.
I have a LoginController/Index (two actions, one for HttpGet and one for HttpPost). The View of this controller represents the application's login page.
I also have a home page or landing page, i.e. the page that the user must be taken to after a successful login. This is represented in my application by the HomeController's Index action and the ~Views/Home/Index.cshtml view.
I have presented three scenarios. I understand scenario 1 and I expect it to work the way it does, but I noted a difference in scenarios 2 and 3.
Scenario 1
namespace Controllers
{
[AllowAnonymous]
public class LoginController : Controller
{
[HttpPost]
public ActionResult Index(Login loginViewModel)
{
if (ModelState.IsValid)
{
var user = ValidateUser(loginViewModel);
if (user != null)
{
// Other stuff: set cookies, session state, etc.
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Invalid password. Please try again.");
}
}
// If the user was a valid user, the flow-of-control won't reach here
// as expected and the user will be taken to the view that is served
// by the HomeController::Index() action. If it is by convention, it will
// be the ~Views/Home/Index.cshtml view. This is fine.
return View();
}
}
}
Scenario 2
namespace Controllers
{
[AllowAnonymous]
public class LoginController : Controller
{
[HttpPost]
public ActionResult Index(Login loginViewModel)
{
if (ModelState.IsValid)
{
var user = ValidateUser(loginViewModel);
if (user != null)
{
// Other stuff: set cookies, session state, etc.
Response.Redirect(FormsAuthentication.GetRedirectUrl(loginViewModel.UserName,
loginViewModel.RememberMe));
}
else
{
ModelState.AddModelError("", "Invalid password. Please try again.");
}
}
// If the user was a valid user, the flow-of-control still reaches here
// as expected. And as expected, it renders the same View, i.e. the View
// associated with the controller we are in, which is ~Views/Login/Index,
// which represents the login page. This is wrong. I shouldn't redirect here.
// I understand this. My question here is two fold:
// 1) I am simply trying to understand the difference in behaviors of the three
// scenarios described in this question.
// 2) Given this, the right way would be to not use Response.Redirect here but instead
// use RedirectToAction. However, if I wanted to use Response.Redirect, what should
// I do?
return View();
}
}
}
Scenario 3
namespace Controllers
{
[AllowAnonymous]
public class LoginController : Controller
{
[HttpPost]
public ActionResult Index(Login loginViewModel)
{
if (ModelState.IsValid)
{
var user = ValidateUser(loginViewModel);
if (user != null)
{
// Other stuff: set cookies, session state, etc.
FormsAuthentication.RedirectFromLoginPage(loginViewModel.UserName,
loginViewModel.RememberMe);
}
else
{
ModelState.AddModelError("", "Invalid password. Please try again.");
}
}
// If the user was a valid user, the flow-of-control still reaches here
// as expected. However, magically, somehow, even though the statement below
// suggests that the user must be taken to the View of the same controller and
// action that we are currently in, i.e. the View of the LoginController::Index()
// action, i.e. the ~Views/Login/Index.cshtml, it magically takes me to the
// ~Views/Home/Index.cshtml instead, which is what is specified as the LoginPage
// attribute of the <authentication>/<forms> element in the web.config.
// I want to know how this happens.
return View();
}
}
}
Update
I am at my wit's end now. Now, even Scenario 1 that uses RedirectToAction is calling the Index() action on the LoginController class.
The actual difference is that FormsAuthentication.RedirectFromLoginPage() sets cookies and then makes redirects but FormsAuthentication.GetRedirectUrl() only returns redirect url.
The funny thing is that implementation of FormsAuthentication.GetRedirectUrl() is like this:
public static String GetRedirectUrl(String userName, bool createPersistentCookie)
{
if (userName == null)
return null;
return GetReturnUrl(true);
}
So actually userName and createPersistentCookie parameters are completely ignored. You must call FormsAuthentication.SetAuthCookie( userName, true/false ) manually before calling GetRedirectUrl.
Agree with Vasily.
RedirectFromLoginPage issues an authentication ticket and places it in the default cookie using the SetAuthCookie method.
You can read something about this behavior here.
If you want to have a better control over the cookie creation you should (encryption, expiration, extending the principal) you should create the cookie yourself.
I explained the whole process here and here.

Resources