Yash Murty
Yash Murty
Creator of this blog.
May 29, 2020 7 min read

Backend for Japan's Contact Tracing App

thumbnail for this post

Brief Introduction

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:
https://github.com/mamori-i-japan/mamori-i-japan-api

Workflow & Business logic

The following interactions are possible between the mobile app & backend

  1. Perform login via the backend api
  2. Update profile attributes via backend api
  3. Fetch profile info directly via FirestoreDB
  4. Upload self diagnosis-keys via backend api (Needs to pass health-center-token in payload)
  5. Fetch diagnosis-keys JSON file of positive reported persons via firebase storage
  6. As an asynchronous step, we run cronjobs to validate health-center-token and also to create the diagnosis-keys JSON file
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.

Authentication

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.

5. Fetch 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-keys collection 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-token with the API provided by the Japanese government.

  • validate health-center-token
    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):

Data-Flow-Diagram

Side Note:

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

Technology stack

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.

Nest (NestJS) is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with and fully supports TypeScript (yet still enables developers to code in pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
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!

Link to NestJS Framework

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 Serverless Framework.

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.

Link to Serverless Framework

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.

Link to AWS Systems Manager Parameter Store

This basically covers the technology stack that was used for our backend.

Spec-In-Progress

The 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. Such a health-center-token exists to prevent spam positives from bad actors.

Load testing

The QA team performed load testing on the APIs using various tools like k6, JMeter, wrk2, Locust, Artillery, Loadroid. 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.

Work-In-Progress

  • Unit tests
  • Application process monitoring

Contributors

The project is now open source, a big thank you to the contributors so far:

comments powered by Disqus