Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keeping state of logged in user in ASP .Net Core Web API

I have an ASP .Net Core 1.1 Web Api, which serves requests from a front end web app and phone app. The app is for real estate agents to perform inspections on properties. The system therefore has entities such as inspections, properties, users, agencies, etc. Users (i.e. real estate agents) and properties belong to agencies. So an estate agency can have one or more users and one or more properties.

As an example, a user might want to view a list of all properties inspected by anyone in his estate agency. In this case, he clicks somewhere in the web/phone app, and the app sends off an HTTP GET request to the API to retrieve the list of properties. The Web API, upon receiving this request, first checks which user is requesting this, and then checks which agency this user belongs to, and then returns all the properties that belong to this agency. So... these are the [simplified] models that I have:

[Table("agency")]
public class Agency
{

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("agency_id")]
    public int? Id { get; set; }

    [Column("name")]
    public string Name { get; set; }
}

[Table("user")]
public class User
{

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("user_id")]
    public int? Id { get; set; }

    [Column("username")]
    public string Username { get; set; }

    [Column("agency_id")]
    public int AgencyId { get; set; }

    [ForeignKey("AgencyId")]
    public Agency Agency { get; set; }
}

[Table("property")]
public class Property
{

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("property_id")]
    public int? Id { get; set; }

    [Column("address")]
    public string Address { get; set; }

    [Column("agency_id")]
    public int AgencyId { get; set; }

    [ForeignKey("AgencyId")]
    public Agency Agency { get; set; }
}

The controller action on the Web API that handles the aforementioned request looks like this:

[Authorize]
[HttpGet]
public async Task<IActionResult> GetProperties()
{
    // Get Auth0 identifier for the user performing the request
    string nameIdentifier = User.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
    // Retrieve this user from the database
    User user = await _context.User.SingleOrDefaultAsync(u => u.Username == nameIdentifier);
    // Return a list of properties that belong to the user's agency
    return Ok(_context.Properties
        .Where(p.AgencyId == user.AgencyId));
}

Now, all GET requests to the API are filtered like this - where the API returns records that pertain to the logged in user's agency. Whether the user requests a list of inspections, or landlords, or tenants, the API always returns only records that belong to the user's agency.

Now I know ASP .Net Core is stateless, so I can't save the user object on the server when the user logs in, and have it in memory ready to be called in each controller action. But I feel like this round trip to the database EVERY time the API receives any GET request to find out which agency this user belongs to is wasteful. Is there a better way to do this? I'm fairly new to ASP and web development in general. Any ideas/hints/pointers would be greatly appreciated.

like image 848
Fabricio Rodriguez Avatar asked Jan 30 '23 01:01

Fabricio Rodriguez


1 Answers

Now I know ASP .Net Core is stateless, so I can't save the user object on the server when the user logs in, and have it in memory ready to be called in each controller action. But I feel like this round trip to the database EVERY time the API receives any GET request to find out which agency this user belongs to is wasteful.


Welcome to the web, it is a stateless environment. If you need the user record to handle each http request you basically have a few choices:
  1. Get it from the database on ever request
  2. Place the part of the record you need into session
  3. Place the part of the record you need into a cookie

Each approach has pros and cons.

Option 1 will give you the absolute latest info from the user record but comes at the cost of an additional database round trip for ever request. And if you have multiple different records you need to handle the request you may end up with multiple database round trips for each request depending on how you architect things,

Option 2 can eliminate that database round trip for ever request if you have your Asp.Net Core app configured to use the In-Memory Session Provider. But the downside is that if the web app is recycled (which is pretty common if running behind IIS) then session will be cleared. If you have session configured to use a database provider like SqlServer then you will still incur a database round trip for each request as it reads in session from the database (but that will be only one round trip instead of possibly several depending on what you have placed in session and how many db round trips to took to compile the info.) A downside to placing info in database backed session is that it forces a database round trip for every request even if the request doesn't need any of the info that is placed in session. And if you are not careful the size of the data stored in session (and queried from the database each request) get quite large quickly. It can sometimes be too easy to "just throw it into session" which can lead ultimately to performance problems.

Option 3 places the needed data in a cookie that will travel along with each request in the header of the http request. The downside of this is that it makes every file request marginally larger (often even image requests) and you will probably want to limit the cookie to only https requests to keep the info from being transmitted in the clear over the internet. This approach is typically only used for very small amounts of data.

This summary is in no way exhaustive, there are additional ways to keep track of state (query parameters, etc) and additional security considerations for any method chosen. The main takeaway is that, yes the web is a stateless environment, but there are still ways to keep track of state, however all of them have pros and cons and no single way is always the best. Most large web apps use variations of all the available approaches for different aspects of their web application.

You can read more about Asp.NET Core session and application state here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state

like image 116
RonC Avatar answered Feb 01 '23 17:02

RonC