We’re developing multitenant Angular Web App which uses Azure DocumentDb as a storage. Documents from all tenants of the app stored in the same collection and distinguished by tenantId attribute of the document. We will have middle tier .NET service which holds master key to DocumentDb and exposes REST API endpoints for CRUD operations by Web App. Users of the Web App is authenticated by oAuth providers (Google, Facebook, Microsoft). What is the best practice for securing tenants data so users of one tenant cannot access data from other tenants?
Question posted in Facebook API
The official documentation for the Facebook APIs can be found here.
The official documentation for the Facebook APIs can be found here.
2
Answers
I’m not confident enough to call it best practice, but we have a middle layer REST API like you do and here’s what we do:
Clients (even browser code) can submit arbitrary queries. We use sql-from-mongo so these are in mongo-like syntax which is easier for javascript clients to build a query but what I’m suggesting would be just as secure using raw SQL queries as long as you parameterized the variables and you disallow any projections in your
SELECT
clause. This last part we accomplish in the sql-from-mongo translation, but you could strip out the projections (replace the providedSELECT
clause withSELECT *
) or reject any query that didn’t start withSELECT *
. This is critical because without it, bad actors could project their owntenentID
oruserID
that the rest of these controls keys off of.Our REST middleware caches the tenant and user context for each client and these contain our authorization specification (similar in concept to Firebase’s). In the simplest form, the user is tied to a single tenent and that user has read permission for all the data for that tenant, but you can specify anything.
When the query is executed, the returned dataset is checked against the authorization specification. Depending upon the situation, requests with any amount of disallowed documents are either rejected completely or the disallowed documents are filtered out of the result set that we return.
On the write side of things, we do something similar. We check the update against the authorization specification and reject any that don’t comply. It’s a bit more complicated than that because we’re using a sproc for all writes and we have a mongo-like syntax for in-place updates and we implement cross-document ACID for all writes (using the sproc), but you can do what I’m suggesting without all that.
Hope this helps. Feel free to ask questions in the comments.
This can be achieved by logically intercepting the requests in the middle-tier on both read&write, and by keeping a tenantId tag in each document.
Let’s start with the read path. Assume each document has tenantId property set to the corresponding tenant of the user that the document belongs to. To read any data, go via parameterized SQL query (https://azure.microsoft.com/en-us/blog/announcing-sql-parameterization-in-documentdb/) and always ensure that the query has tenantId filter. This will make sure that the user’s request only deals with the tenant ID that the request is supposed to look at. Parameterization is important to avoid injections and there by getting access to other tenant’s data.
On the write path, you need to make sure that each document has the tenantId property correctly set. If the end client doesn’t set or even if it does, the mid-tier should parse this and ensure it matches with the tenant corresponding to the user’s authorization token that you get back from the OAuth provider.
In this context, note that partition key of tenantId would help keep all the data of a single tenant together. This is helpful for efficient queries in terms of zeroing on a single partition, as long as the query has the tenantId filter.