Web API - Designing outer facing model issue
Wondering if someone could help point me in the right direction. My post on stackoverflow didn't gain much traction and most comments were more akin to don't do this as opposed to a direction to take. I am using ASP.Net Core 2.1 and Entity Framework Core. API will be used by a web application that I will also be building (using a mix of Asp.Net core 2 and some JS).
I have an event class that references who the user was that created it (User id + navigation property of user to my user entity). Anytime the API is called using PUT/PATCH/DELETE, I want to prevent users from being able to update the user id field and deleting other users events. The API can be called with all the HTTP verbs for example a GET request to api/events to list all events or a POST to api/events to create an event.
My event entity:
public Guid Id { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public string Url { get; set; }
public DateTime Created { get; set; }
public DateTime ScheduledTime { get; set; }
public Guid UserId { get; set; }
public User CreatedBy { get; set; }
Event update data model (for PUT/PATCH requests):
public string Title { get; set; }
public string Message { get; set; }
public string Url { get; set; }
public DateTime? ScheduledTime { get; set; }
My user entity
public Guid Id { get; set; }
public string Email { get; set; }
public List<Event> Events { get; set; } = new List<Event>();
The route for my controller is very simple: ** [Route("api/[controller]")]** so its accessed by ** api/events**.
Currently the approach I have taken is to explicitly pass in the user id to the API. In the action, I fetch the event from the database, compare the values of the user id that was passed in to the user id fetched from the database. If its the same, I allow the update to happen. If its different, I return a 403 forbidden along with an error message.
I.e. My update action in the API:
[HttpPut("{id}")]
public async Task<IActionResult> UpdateEvent([FromRoute] Guid id, [FromQuery] Guid userId,[FromBody] EventUpdateDto eventToUpdate)
{
//compare userId passed to action by fetching event from database and compare values
//return 403 if not equal else continue
}
The problem is that I am using both route parameters and query string parameters. I can simplify the route to be {eventid}/{userid} but I don't know if this is best practice. This would mean that I am calling my API to update a specific event but then also drilling into a specific User which does not make much sense. I also didn't want to go the other way of api/{userid}/events/{eventid} for my controller because I want to access all events for all users or a single event in my web application - I don't want it to be limited to only pull events for a single userid.
I also have the same issue with DELETE as I don't want a user deleting other users posts. So its setup as below:
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteEvent([FromRoute] Guid id, [FromQuery] Guid userId)
Note that my update model does not contain UserId just because I wanted to prevent it from being overridden. I came up with 3 separate approaches to this issue and am wondering which one is the correct path to take and if there is a better design for this.
1) Remove any check for userid validation from the API and place logic into the web application that will be calling the API. With this approach, every time a user tries to edit an event in the application, I'll first have to fetch the event from the API, compare the current userid to the userid fetched from API and either let them continue or throw error. I have scaling concerns with this
2) Store some form of userid in the JWT used by the API and access it from the JWT to verify userid
3) Keep my current approach.
I am a bit hesitant on approach #1 due to possible performance issues along with wanting to have an API that is self-contained so that it can be plugged into other applications without having to write complex logic in the web app. Is one approach better than the other? Is there a better approach I can take with designing my outer facing models? Any help on the design would be very helpful.
edit: Anyone know what the best practice is for session data storage? Memory storage is not recommended do is something like a redis cache usually best practice? Also are there any .net specific discords? Would be really nice to discuss design ideas as opposed to questions on why code is not working.
0 comments:
Post a Comment