Results 1 to 4 of 4

Thread: [RESOLVED] Multi-Tenant Authorization Response

  1. #1

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Posts
    12,370

    Resolved [RESOLVED] Multi-Tenant Authorization Response

    I am building a multi-tenant web application and I currently have my login endpoint setup like this:
    Code:
    [HttpPost("Login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
        {
            return BadRequest("Username and password are required.");
        }
    
        var result = await _authService.LoginAsync(request.Username, request.Password);
        if (result == null)
        {
            return Unauthorized("Username and/or password is incorrect.");
        }
    
        var accountUser = result.Value.accountUser;
        var tenantUser = result.Value.tenantUser;
        if (!accountUser.IsSystemUser && tenantUser == null)
        {
            return Unauthorized("User is not authorized to view this tenant.");
        }
    
        return Ok(new
        {
            user = accountUser,
            tenant = tenantUser
        });
    }
    The way it works is that I have an AccountUser which stores the username/password information and then the TenantUser which stores the AccountUser/Tenant relationship mapping.

    With my code above, its possible that the user successfully logs in, but doesn't have a TenantUser for the current tenant (which is determined via middleware by the subdomain of the request). This is where my last if/then statement lives:
    Code:
    if (!accountUser.IsSystemUser && result.Value.tenantUser == null)
    {
        return Unauthorized("User is not authorized to view this tenant.");
    }
    What I'd like to do is send something back to the client making the request that "hey, you're username and password is correct, you just don't have access to this tenant, let me redirect you to the tenant selection page".

    I don't think that Unauthorized is the correct response here because I'm using it to say "hey, you either don't have an account or that password's wrong buddy". But at the same time I don't think a redirect response is appropriate because I have a clear separation between my API project and my UI project.

    What would be the appropriate approach here?
    Last edited by dday9; Jul 29th, 2025 at 08:39 AM.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | HtmlLessons | CssLessons | Code Tags | Sword of Fury - Jameram

  2. #2
    PowerPoster
    Join Date
    Nov 2017
    Posts
    3,630

    Re: Multi-Tenant Authorization Response

    HTTP Error 400 = Bad Request
    HTTP Error 401 = Unauthorized

    From an HTTP error standpoint, what you are looking for is a 403 Forbidden error. I don't know ASP.NET Core, so I can't assist further than that.

  3. #3

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Posts
    12,370

    Re: Multi-Tenant Authorization Response

    That makes sense. I can return a Forbidden response with a body that includes the AccountUser record:
    Code:
    return StatusCode(StatusCodes.Status403Forbidden, new 
    {
        user = accountUser
    });
    Then from the client, I can handle that specific status code. If the body is set and the user is present then I can then prompt the user to be redirected to the tenant selection page.

    Does this make sense to you? Sorry if that sounds juvenile, I'm really trying to implement sound architectural decisions in standing up this project.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | HtmlLessons | CssLessons | Code Tags | Sword of Fury - Jameram

  4. #4

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Posts
    12,370

    Re: Multi-Tenant Authorization Response

    So I really like this approach and ran it past ChatGPT. It gave me a few suggestions and this is what I ended up with:
    Code:
    [HttpPost("Login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
        {
            return BadRequest("Username and password are required.");
        }
    
        var result = await _authService.LoginAsync(request.Username, request.Password);
        if (result == null)
        {
            return Unauthorized("Username and/or password is incorrect.");
        }
    
        var accountUser = result.Value.accountUser;
        var tenantUser = result.Value.tenantUser;
        if (!accountUser.IsSystemUser && tenantUser == null)
        {
            return StatusCode(StatusCodes.Status403Forbidden, new
            {
                success = false,
                user = accountUser,
                tenant = (TenantUserViewModel?)null
            });
        }
    
        // TODO: issue JWT
        return Ok(new
        {
            success = true,
            user = accountUser,
            tenant = tenantUser
        });
    }
    I think semantically it makes sense because a 400 is returned if the required fields are not present, a 401 is returned if the user doesn't exist or the password is invalid, a 403 is returned if the user exists and the password is valid and the user isn't a system user and the user doesn't exist for that tenant, and finally a 200 is returned for all other scenarios.

    This will allow me to specifically handle 403 responses so that I can then redirect them to a tenant selection page which will fetch all tenants that the user has a TenantUser record for and then hit the login endpoint for that specific tenant which will then hit the 200.

    I'm going to close this out, but if anyone else has any opinions, feel free to chime in since I'm subscribed to the thread.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | HtmlLessons | CssLessons | Code Tags | Sword of Fury - Jameram

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width