Recently, I had a talk about a Mobile App that I have developed for personal needs. I initially planned to replay this talk but given the lockdown precaution measures, I thought it'd be better to make a blog post out of it. The idea is to walk you through the architecture behind this multi-tenant mobile app and explain the rationale behind every single choice.
The Use Case
As a wine lover, it was about time for me to build something new to manage my cellar. There are plenty of apps available on Google Play but I wanted to add my own bits (and wine skills) into the app to serve me exactly as I wanted. I built the app for myself and then, some friends asked me to get it too but...it was not multi-tenant...so I decided to make it multi-tenant, and although it is a hobby project, I wanted to build it in a professional way. That said, should you be excited by this blog post, don't try to look for the app as I haven't published it on Google Play as I don't want to pay for the Azure costs should many folks install it, so right now I just let my friends download the app :).
I wanted an app that:
My goal is to make sure that the shared backend remains healthy and secure. Any attempt from a malicious user to hack my backend should result in having that user just messing up with his own tenant while not impacting others. With that in mind, I came up with the following architecture:
At first sight, it may look simple but it is a little more complicated than it seems. Let me explain all the numbered bullet points; the implementation details will follow:
So, the key aspects here are: I used the network here and there to maximize the security but I mostly rely on identity & MSI. I'm using Microsoft managed keys for encryption since data is certainly not "classified" information . The only sensitive information disclosed to the mobile device is tenant (user in this case)-specific. The Design is not Disaster Recovery ready but who cares for such an app.
The implementation details
Now that we have seen the global architecture, let's zoom into some implementation details. I will only highlight the most important parts. The first and important step in terms of security is about the user registration whose the flow is as follows:
The important bits here are about the retrieval of the tenant-specific API subscription keys. In my particular case, each user is a tenant and is the wine cellar owner. Therefore, upon registration, the system creates an API subscription on the fly and returns one of the generated subscription keys to the device, which stores it locally as long as the key is valid. The backend subscription service (item 10 in the architecture diagram) is protected by a facade API enforcing a JWT validation against our B2C directory.
Here is an example of such a JWT token, requested by the mobile app, to access any of my APIs on behalf of the logged in user:
The token must contain the managecellar scope and of course be issued by my B2C directory with the valid audience (aka client app). Any request to the subscription service containing such a valid access token is forwarded to the subscription service. A throttling limit of 10 requests/minute/user is set to avoid abuse of the subscription service:
<rate-limit-by-key calls="10" renewal-period="60" counter-key="@(context.Request.Headers.GetValueOrDefault("Authorization","").AsJwt()?.Subject)" />
On the subject claim, highlighted in the above picture. So, here again, anyone monitoring the traffic would see his own access token but could only throttle himself should he try to play it the dirty way, while nor my backend service, nor other users will be impacted.
More globally, the facade APIs are doing a lot of the heavy lifting and are organised this way:
An API product applying multiple policies such as IP Filtering on the Front Door service and JWT token validation since none of my APIs can only be accessed using the subscription key only. The product only contains the Wine & OCR APIs. Indeed, my subscription service's facade API is subscription-free since its primary purpose is to return the tenant-specific subscription keys. Therefore, I cannot reuse my product policies and have to replay them locally. The OCR facade API is first extracting the shared Cognitive Service API key from Key Vault and forwards the request to the Cognitive Service.
Last but not least: Cosmos DB! Why did I choose Cosmos DB? I see many folks rushing to NoSQL and quickly facing issues because of poor up-front engineering. In this particular use case, Cosmos is perfect for the following reasons:
My scenario is perfectly in line with those three rules of thumb. I am using the Tenant (aka subject in my access token) as partition key. I'm okay with rule 1 because I can hardly imagine to have a user with a cellar of 1 000 000 bottles and another one with only 1 bottle. So, globally, the load will be evenly distributed across logical partitions.
I'm okay with Rule 2 because I'll include my partition key in every single query since I want to make sure that users can only see their own wines. At last, since I'm storing the pictures in a Storage Account, I can hardly imagine that a single cellar would need more than 20GB.... given my wine document is just about text metadata.
I think this kind of approach is suitable for other B2C scenarios; that's why I wanted to share it!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.