Backend for Japan's Contact Tracing App
Since March 2020, I have been a part an initiative to develop the contact tracing app for Japan a.k.a Mamoriai Japan. The team behind it was formed under the leadership of Code for Japan (CFJ) and is around 50+ members strong. It comprises of engineers, designers, marketing experts, doctors and other domain experts.
The initial approach by the Japanese government was to allow multiple apps in the market which match certain guidelines and privacy expectations. But later after the introduction of new specifications by a joint initiative between Apple & Google, it was declared that only one app would be allowed per country to to have a better impact.
Following this constraint, the Japanese government decided to proceed internally with the development of the app, and hence CFJ decided to make all of it’s development efforts until that point as public with an open license. The goal of this open sourced app is to act as a reference point for the actual app which will now be developed by the government.
With this brief introduction, I would like to talk further about the backend itself and some choices about our technology stack.
The backend repository is located in the following github repository:
Workflow & Business logic
The following interactions are possible between the mobile app & backend
- Perform login via the backend api
- Update profile attributes via backend api
- Fetch profile info directly via FirestoreDB
- Upload self
diagnosis-keysvia backend api (Needs to pass
diagnosis-keysJSON file of positive reported persons via firebase storage
- As an asynchronous step, we run cronjobs to validate
health-center-tokenand also to create the
1. Perform login via the backend api
In this step, the user logs in to the backend using a default firebase token. This default firebase token is received on the mobile app by using Firebase SDK and performing anonymous login first. It is then sent to the login API. The backend verifies it using Firebase Admin SDK. Upon successful login on the backend, the backend adds a custom claim to the user, which is reflected in the users JWT after it is refreshed.
All other backend API endpoints expect the JWT to have this custom claim, and if missing, it invalidates the request. For more details, refer to this PlantUML diagram.
2. Update profile attributes via backend api
This is just an endpoint to provide CRUD functionality for the user’s profile. Mainly used for updating a users prefecture location, which is in turn used for showing custom message from the health authorities in that prefecture.
3. Fetch profile info directly via FirestoreDB
We decided to read the profile info directly via Firebase SDK to reduce the load on our backend APIs.
4. Upload self
diagnosis-keys via backend api (Needs to pass
health-center-token in payload)
One of the most important steps is achieved by this API endpoint. Once a user finds out from a health authority that they have been diagnosed as positive, they can proceed to upload their self
diagnosis-keys (previously termed as temporary IDs) to the backend API.
This upload is performed along with passing a
health-center-token received via the health authority upon receiving the positive result. This step is done to prevent SPAM positive uploads by bad actors in the system.
diagnosis-keys JSON file of positive reported persons via firebase storage
In this step, the mobile app periodically fetches the JSON file which consists all the
diagnosis-keys which were uploaded as positive. This data is matched against the locally saved
diagnosis-keys, which are received from users when physically exposed for certain time/distance.
Upon a successful match, a notification is shown to the user asking them to take further action and contact the health authorities themselves.
6. As an asynchronous step, we run cronjobs to validate
health-center-token and also to create the
diagnosis-keys JSON file
We have a total of 2 cron jobs planned, of which only 1 is implemented so far:
create the diagnosis-keys JSON file
This cronjob periodically checks the
diagnosis-keyscollection in FirestoreDB and creates a JSON file by fetching the keys for the last 14 days. It only fetches those keys which have verified the
health-center-tokenwith the API provided by the Japanese government.
This cronjob has not been implement yet. Please check the Spec-In-Progress section below.
Here is an attached Data-Flow-Diagram showing the above steps (Sorry for the small font size, please view it using this Miro tool link):
Within the repository, there is also some great backend logic for handling access control for admin users. But it seemed a bit irrelevant to talk about in this post, so we will skip it. Link to ACL logic diagram
Given the scope of traffic that this project may possibly have, one of our core architecture principles was to keep scaling/infrastructure management to a minimum. We decided to proceed with a serverless architecture, and since we have past experience of doing the same using NodeJS, we decided to proceed with it. We decided to utilize the latest NestJS Framework this time.
Under the hood, Nest makes use of robust HTTP Server frameworks like Express (the default) and optionally can be configured to use Fastify as well!
To deploy our backend to AWS Lambda, we decided to use Serverless Framework. This was really a great choice because over the course of our project, we changed our serverless platform from
AWS Lambda to
Azure Cloud Functions, then to
Google Cloud Functions, and then back to
AWS Lambda. We were able to support these changes with ease thanks to the
Develop, deploy, troubleshoot and secure your serverless applications with radically less overhead and cost by using the Serverless Framework. The Serverless Framework consists of an open source CLI and a hosted dashboard. Together, they provide you with full serverless application lifecycle management.
For user session management, we decided to use Firebase Authentication. This decision was primarily made because we initially supported mobile number based login, but later removed it and converted it to anonymous login. Again, supporting the changes in specs was a breeze thanks to the
Firebase Authentication framework.
For file sharing, we decided to use Firebase Cloud Storage. This decision was made because we wanted to limit the access to only authenticated users with some custom claims which are easily supported by
Firebase Cloud Storage security rules.
For secrets management, we decided to use AWS Systems Manager Parameter Store.
AWS Systems Manager Parameter Store provides secure, hierarchical storage for configuration data management and secrets management. You can store data such as passwords, database strings, Amazon Machine Image (AMI) IDs, and license codes as parameter values.
This basically covers the technology stack that was used for our backend.
health-center-token validation is not implemented yet because the Japanese government did not disclose the API specifications for the same. Currently we assume that the
health-center-token passed while upload diagnosis keys is always valid and correct.
health-center-token exists to prevent spam positives from bad actors.
The QA team performed load testing on the APIs using various tools like
The gist of the load testing was that our backend easily scaled to 10,000 requests/second, only to hit a roadblock of usage limits by Firebase FirestoreDB’s free tier.
I’d like to add more numbers and graphs from the load test, but unfortunately the compiled test report is in Japanese language.
A basic penetration testing report is also available for reference.
- Unit tests
- Application process monitoring
The project is now open source, a big thank you to the contributors so far: