API Endpoints and Structure - api

I am creating a Web Api with ASP.NET Core and I have the following:
public class MessageApiController : Controller {
[HttpGet("messages/{id}")]
public async Task<IActionResult> GetById() { }
}
public class UserApiController : Controller {
[HttpGet("users/{id}")]
public async Task<IActionResult> GetById() { }
}
Now I need to get all messages for a user. I know two options:
[HttpGet("messages")]
public async Task<IActionResult> GetMessagesByUser(Int32 userId) { }
Or
[HttpGet("users/{userId}/messages")]
public async Task<IActionResult> GetMessagesByUser(Int32 userId) { }
I have a few questions:
Which is more common? I think I could even use both ...
When using "users/{userId}/messages" would you place this action in MessageApiController or UserApiController?

It depends on how you want your consumers to use the service.
The question you need to ask yourself, does it make sense to call /messages to obtain all messages (from all users) or should it only be ever possible to obtain messages from 1 user (and exclude the possibility to ever obtain messages from all users).
This question is important, because it will tell you which one to use or if you should use both.
When there is no plans to ever allow users to obtain message from all users, you better go with users/{userId}/messages because this will enforce this rules. In this case users/{userId}/messages is a resource of messages for user userId.
On other side, the expectation is that messages will allow you to query all users messages or at least the ones the user has permission too (i.e. an administrative user would get all users messages back on this endpoint) and you could filter this via query parameters (rather than encoding in the path). If you do not want this, then messages will be an invalid route or just misleading.
Which is more common? I think I could even use both ...
users/{userId}/messages is more expressive and from the url it's clear, that you want all messages from a given user.
messages can be used, if you also have a permission system to allow certain users see messages from other users (i.e. a companies worker should be able to see all messages of the company, not just the ones sent directly to him) with query filters to reduce this (by single or multiple users).
When using "users/{userId}/messages" would you place this action in MessageApiController or UserApiController?
To MessageApiController, because it's operating on the message, not on the user. The user is just a mandatory parameter.
On a side note:
You can simplify your controller routings by putting a route on the controller type rather than having the route template completely on the action.
[Route("[controller]")]
public class MessageController : Controller
{
[HttpGet("id}")]
public async Task<IActionResult> GetById()
{
}
}
Notice that I removed the "Api" from controller name, because the [controller] placeholder takes the class name minus "Controller" as route parameter. The Route on MessageController acts as prefix, so GetById() still gets message/{id} as a route, but you don't have to repeat the "message" part on each action.

Related

Is it possible to get the UI culture of a SignalR client to send localized content?

It's easy enough for me to send messages via SignalR to specific users:
await _hubContext.Clients.User(userIDString).SendAsync("remoteMethod", data);
The challenge is that I can't figure out how to send that message string accounting for the user's culture (en, fr, nl, es, etc). With a straight up hit to a regular endpoint, you just use Resources.PropertyName and it magically works based on the user's browser culture. But if I want to tailor the data above to the user's culture, I don't know how. In fact, if this code fires as a result of a different user, the current UI culture is of that user.
For httpconnections,you could also get httpcontext with GetHttpcontext() method
And here's the document related:
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-6.0#the-context-object
I tried as below in hub:
public class ChatHub : Hub
{
public string Culture()
{
var culture = this.Context.GetHttpContext().Request.Headers["Accept-Language"].ToString();
return (culture);
}
public async Task SendMessage(string user, string message)
=> await Clients.All.SendAsync("ReceiveMessage", user, this.Culture());
}
The test result:
As it turns out, it makes sense that what I'm asking isn't easily possible. Each registered client has little more than an ID to work with. While you could do some magic with an implementation of IUserIdProvider to map userID's to culture, you would then have to maintain extra state to do so, and I didn't want to be in that business.
My solution was to simply serialize a bunch of resource strings and have the client call them from a controller action. I have the client cache it with [ResponseCache(Duration = 600, Location = ResponseCacheLocation.Client)], and the resulting Javascript object is a global variable available to all the things on the client.

How to properly do resource-based authorization in ASP.NET Core 2.2?

I have an ASP.NET Core 2.2 application where I am displaying some documents. Most documents are public, so anonymous access is fine. However, some documents are private (i.e. they require authentication/authorization), and in the future some documents might also require a valid subscription. All documents are retrieved using the same actions, so we only know the required permissions after the documents have been loaded. We also load some resources as static files (IApplicationBuilder.UseStaticFiles), but I guess that shouldn't really be an issue as StaticFileOptions.OnPrepareResponse can be used for custom authorization code.
The logic for who gets access to private documents is currently really simple. And at the moment, we only display documents, we don't allow any other kind of operation on them (editing, deletion etc.). To me, this sounds like a pretty standard case of resource-based authorization.
Anyway, I have found this article and from what I've understood, I need to define a policy (identified by a magic string - what's up with that?!) as well as a requirement and an AuthorizationHandler<MyRequirement, MyResource> which will perform the actual authorization logic. Then, inside my controller action, I will need to call IAuthorizationService.AuthorizeAsync and pass in the user, the resource and the policy name (the magic string) and, based on the result from that method, allow or deny access. That seems more than convoluted for what I'm trying to accomplish. It would probably be easier if I simply defined my own kind of "authorization service" and simply dropped the whole policy and requirement stuff. I also think it's less than ideal that I would have to replicate the if-else logic in all affected controller actions.
Surely I'm not the only one with this issue. Is there something I've missed?
If there are indeed good reasons for using policies and requirements, how would you name them in a case like this? I'm really feeling a little lost.
Maybe it would make sense to use the type of document (public, private, subscribers-only) as the policy name?
In the end, we didn't want to deal with this stuff and just wrote our own AuthorizationService, which is injected into the controller like any other service.
It loads the required permissions for all documents the first time it is used and caches them.
Our controller methods then look something like this:
[HttpGet("[action]")]
public async Task<Document> GetDocument(string documentId)
{
if (_authorizationService.MayAccess(User, documentId))
{
return _documentRepository.GetDocument(documentId);
}
else
{
Response.StatusCode = StatusCodes.Status403Forbidden;
return null;
}
}
I recommend the last approach explained in this article - https://www.red-gate.com/simple-talk/dotnet/c-programming/policy-based-authorization-in-asp-net-core-a-deep-dive/
Allows you to keep you controller clean, by just applying annotation with the name of the policy. In the handler you must implement the logic checking if person can access the resource - it can be based for example on checking a property ownerId in a resource(for example in database table column) or a member of a certain group in AD, or anything else.
EDIT:
Using Requirements and RequirementsHandlers - I have done something similiar.
I don't know how should your logic exactly work, so I am just going to assume some.
lets say you have a get endpoint: documents/documentId
You want to apply logic which will make this document accessible only to the document owner. Obviously, you need somewhere to store who is the owner of the document, so lets keep that in property of a document entity.
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IsDocumentOwner requirement, DocumentRepository documentRepository)
{
if (context.Resource is AuthorizationFilterContext ctx)
{
var documentId = ctx.RouteData.Values["documentId"]?.ToString();
//here load document from repo and check if the property ownerId is equal to current user id
var userId = context.User.Claims.FirstOrDefault(x => x.ToString().Contains(oid))?.Value;
//if yes, make the request pass to the body of a controller with the attribute
context.Succeed(requirement);
}
}
implement IsDocumentOwner:
public class IsDocumentOwner : IAuthorizationRequirement
{
}
in your Startup.cs add:
services.AddAuthorization(options =>
{
options.AddPolicy(
nameof(IsDocumentOwner),
policyBuilder => policyBuilder.AddRequirements(
new IsDocumentOwner()));
});
then, last step, apply attribute on your controller method
[Authorize(Policy = "IsDocumentOwner")]
[HttpGet("{documentId}")]
public YourDocumentObjectResultClass GetDocument([FromRoute]string documentId)
{
//stuff you do when current user is owner of the document, probably just display the doc
}
To your IsDocumentOwner handler you can inject any service by constructor(visualised by repository above), for example, to check if the user is a member of a group on azure ad

How to override "OnActionExecuting" defined in the Startup class inside a controller?

This code run for every incoming request to check whether it contains a valid JWT token.
services.AddMvc(options =>
options.Filters.Add(typeof(JwtAttribute)));
That's what I want in most case, except for the first time (when the user is attempting to log in). Because it's running at every request, user can't log in.
I've tried to add an attribute on top of the login action, but it's still not working.
[HttpPost]
[AllowAnonymous]
public async Task<JsonResult> Login([FromBody]Credentials formData)
{
}
What should I do to override the OnActionExecuting in the startup class just in this one case, so that user can log in.
Thanks for helping
By using a custom filter, instead of the built-in authentication and authorization system, you will not be able to use [AllowAnonymous] here since that is directly linked to the auth framework.
What you can do is add additional metadata which you then check as part of your JwtAttribute filter. For example, create another attribute like so:
public class DisableJwtAttribute : Attribute, IFilterMetadata
{ }
You can now add this attribute to your controller action with [DisableJwt].
And inside of your JwtAttribute filter, you can now check for that filter’s existence to stop processing the request. E.g. if your filter is an authorization filter, that would look like this:
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (context.Filters.Any(item => item is DisableJwtAttribute))
return;
// filter is active
}
That being said, the better solution would be to embrace the authentication system and have your JWT validation be part of the normal authentication process. That way, you could actually benefit from all the authentication and authorization things within ASP.NET Core.

.Net core Authorize attribute in inherited controller

I am having some trouble with Authorization policies. I have a baseWebApiController with an action
[HttpDelete("{id}"), Authorize(Policy = "Administrator")]
public virtual async Task<IActionResult> Delete(int id) {}
But in a certain controller which inherits from the above I want to give access to users also, with a policy like:
[HttpDelete("{id}"), Authorize(Policy = "All")]
public override Task<IActionResult> Delete(int id){}
It seems regular users cannot access this action. Do I have to search further for errors in my policy configuration, or since the controller is inherited,m it's attributes are neglected?
Thanks
The AuthorizeAttribute is an attribute that is inherited and that allows itself to be applied multiple times.
That means when inheriting your method which already has an AuthorizeAttribute, that will be carried over. So the final function definition in your subclass would look like this:
[Authorize(Policy = "Administrator")]
[Authorize(Policy = "All")]
public override Task<IActionResult> Delete(int id)
So the route now has two policies in place. This is kind of a problem because policies are designed to be cumulative. So all policies have to pass in order for the authentication to succeed.
Of course, this will not work for you because you actually wanted to “wash out” the original policy from the base class. This is not possible though, so you would have to remove the policy from the base class and maybe introduce a second administrator-only class for those policies.
The general problem here is that your policy design seems to be based on roles. You are using policies, but effectively, you are checking on roles there. Instead, you should consider working with requirements: For example, to delete something, a user would need to fulfill the “DeletionAllowed” requirement. This allows for a much more fine-grained policy system. And the big benefit? Requirement handlers are disjunctive: So one handler that is able to fulfill the requirement is enough to pass it.

What is the best practices to handle multiple form operation under same action in ASP.NET MVC4?

I have the following scenario in my ASP.NET MVC4 project using Razor Engine:
I have a view with at least 4 different forms.
I would like to handle all form POST under same action mapping.
Actually, the 4 forms post to different Route Mapping, as follow:
POST: /User/FilterRolesInUse/15
POST: /User/RemoveRoles/15
POST: /User/FilterRolesNotInUse/15
POST: /User/AddRoles/15
I would like to know if is it possible to handle all 4 form under the same Route Mapping, something where all form post to /User/Roles/15 and then the controller can distinguish which form was submitted. The concept is something like:
class UserController : Controller {
//
// POST: /User/Roles/
public ActionResult Roles(int? id, object form) {
return DelegateToFormLogic(id, form);
}
}
I just want to know if is it possible because I really want to keep URL consistent.
Any advice or suggestion are welcome.
I do not see any advantage to having a single action that performs multiple functions. In fact it will be confusing to anyone that has to support the code. I would get away from submitting forms and use Ajax methods in your web client (using JQuery ajax) to get the data you need for this view and for update/insert/delete actions. This way you do not have to post back the whole page to perform actions that will probably take place on just portions of the view which will result in a better performing page and a better user experience. Change your controller to a ASP.NET Web API controller and make those methods a REST API that uses consistent URL naming convention and use HTTP verbs to indicate the type of action being performed. You will end up 3 methods that serve the 4 you have now and it could look something like this (they correspond to the same order listed in the question).
GET: /api/Role/15?InUse=True
DELETE: /api/Role/15
GET: /api/Role/15?InUse=False
POST: /api/Role
Your controller would look like this.
class RoleController : ApiController {
public List<Role> Get(int id, boolean InUse) { ... }
public void Delete(int id) { ... }
public void Post(List<Role> roles) { ... }
}
This maintains a clear separation of concerns while also keeping a consistent and understandable URL convention.