Yesterday I started a live coding thread on twitter. I implemented multi tenancy into a feathers app. Live coding thread basically means tweeting every step of the proccess and explaining it in a twitter thread. I like this concept a lot and will be doing more of them soon. If you like this concept too, make sure to connect on twitter.
What does multi tenancy mean?
In software development multi tenancy often means that a single application is hosted on one server but serves different tenants. Tenant can mean different things eg a tenant can be a company, an app or a team. Each tenant will use the application as if it's using it alone. It is not connected to other tenants in any way.
What I built
I'm currently building the backend as a service for kiss.js (see my other articles) which is called kisscloud. A single kisscloud instance can host multiple applications. A classic multi tenancy use case. Kisscloud uses feathers.js under the hood. So what I'm actually doing is adding multi tenancy to a feathers.js app.
What needs to be done?
Kisscloud will update everything in realtime. So it uses websockets. In order to add multi tenancy to a websocket based app the follwing steps need to be done:
- I will hook into the socket handshake and add the kissjs appId to the socket connection.
- I will overwrite the default feathersjs authentication to use the appId when requesting session tokens and creating new users.
- And finally I will also add the appId restrictions to every data resource
Adding the appId to the socket connection
Adding the appId to the socket connection is actually pretty easy. On the client I just had to pass it when the socketio connection got configured. And on the server I could register a very simple middleware that gets executed for every incoming websocket.
This is a basic feathers setup. The only thing that happened here is adding the appId to the socket handshake by adding it to the query object.
On the server it's even simpler. This little middleware gets executed for every incoming socket connection. It reads the appId from the handshake and saves it for later use.
The cool part is the feathers attribute on the socket object. It's handled by feathersjs and is made available to almost everything you can imagine. This will come in handy when we try to get access to the appId later.
Access control for data
Access control is very easy with feathersjs. I created 2 hooks, the first adds the appId(already saved to connection after socket init) to every saved resource.
And the second one forces to query for the given appId whenever a query is made for any resource.
That's basically it to ensure that only resources belonging to an app can get loaded and saved.
Now the tricky part:
When signing in, I need to ensure that I query for the username based on the appId. This is pretty easy with feathers. i can extend the local authentication strategy used by feathers and also query for the appId. This way I always load the correct user based on username and on appId:
The heaviest part of all this was creating a new user. The problem is that feathersjs handles the uniqueness of usernames/emails on the database layer. I want to stay database independent with kissjs. So I had to bypass this... First I removed the uniqueness index from the db. At this point there could be unlimited registered users with the same username. What I want is unlimited users with the same username, but each with a different appId. I created another hook for this that gets executed every time a user gets created or updated.
This hook loads a user based on the given username and adds the appId to the query. If a username already exists, the signup flow gets interrupted here. This is not optimal of course, but I think I can live with this solution for now. I can always easily switch back to the database layer when I use a database that can handle unique constraints based on several attributes.
And that's it. With this small changes to the codebase I have complete multi tenancy support.
If you have any additional questions or want to build something similar and need help, just let me know.