How to switch tenant of a logged in user

I want to give the user a tenant switcher, but am not sure what AshAuthentication function to call to do that. My main concern is that I want the current tenant to also be in the “session” not just some Process dictionary like Ash.set_tenant would do. I guess to complicate things even further, I would prefer the “actor” that Ash uses to be the UserTenant resource, but it doesn’t make sense to define authentication in that resource.

Setup: Phoenix 1.7 LiveView + AshAuthentication, w/ context multitenancy

Resources: “User” (has auth pw strategy), and then a “Tenant” and a “UserTenant”, with the “UserTenant” containing extra info, like which role the user has in the current tenant, which impacts policies and such.

AshAuthentication logs in the user, and right now I am selecting the default “UserTenant” in the “success” callback.

Went through ash_authentication v3.12.4 — Documentation and didn’t find anything about this.

Ideally Ash Framework would cover a user being in multiple tenants.

Thank you!

You should be able to do this :slight_smile:

Is the idea to log them into a default tenant to start, and then provide a switcher? Or some kind of intermediate step, i.e after the user logs in, if they are in multiple tenants, redirect to a page where they can select the tenant to log in with?

I don’t think I would necessarily want the actor to be UserTenant, but I can see what you’re getting at. The main thing is that the tenant you’re in should be available pretty much everywhere, so you’d always be able to get the tenant, but also all queries and mutations should already be scoped to the tenant.

I would suggest doing something like this:

  1. if a user only has one UserTenant on login, set that and redirect to the proper place
  2. if they have multiple redirect to a page that lets them select a UserTenant
  3. Once that is set, set it into the session
  4. pass the tenant around in all of your queries, in addition to the actor

Small note: The process dictionary features will be removed in 3.0.

I added a switch tenant route in a restricted section of router.ex, passing the tenant id as a param. Then in the AuthController I validate that and put it in the session. This works, and LiveView picks up the new session data and changes the view. Good so far. I can live with this since it is in the session.

But if I have two tabs open, they are both impacted by changes in each (if I switch tenant in one, the other also changes). Ideally tabs wouldn’t share sessions. I can have two gmail tabs open in the same window and have two different accounts logged in. Is that possible with AshAuthentication?

And is there something special about “tenant” in the session map? It is always nil, even when I set it manually with a put_session call in the AuthController.

%{
  "_csrf_token" => "****",
  "tenant" => nil,
  "user" => "user?id=82249685-****",
  "user_tenant_id" => "d3bebe29-****"
}

Last thing, I can pass in the user_tenant as the actor to all Ash calls, but was hoping there was a better way to make this “just happen.”

And is there something special about “tenant” in the session map? It is always nil, even when I set it manually with a put_session call in the AuthController.

I’m not sure about "tenant" always being nil in the session map. That may actually be something to fix because ash_authentication may be overwriting it? Try setting it into a different key in the session.

But if I have two tabs open, they are both impacted by changes in each (if I switch tenant in one, the other also changes). Ideally tabs wouldn’t share sessions. I can have two gmail tabs open in the same window and have two different accounts logged in. Is that possible with AshAuthentication?

Google does this by storing which tenant you are accessing in the URL. You would need to do the same for this to work. Oftentimes tenants are done using subdomains, i.e tenant.foo.com and so you could do the same. Then the tenant selector would redirect to the subdomain.

Last thing, I can pass in the user_tenant as the actor to all Ash calls, but was hoping there was a better way to make this “just happen.”

What kind of thing do you have in mind for making it “just happen”? Generally speaking, the process dictionary strategy has too many pitfalls and foot guns to make it a good idea IMO, although the functionality is there currently, and before it is removed we will add a hook that will allow you to replicate it (but it will be a “use at your own risk” type thing).