Out of Scope:
[Similarly to other systems, Identity Management is a big, vague topic. It is very important to clearly define the scope and get the interviewer's buy-in. Generally speaking, a good system design interview ends up with a deep conversation, instead of a shallow tour of many topics.]
There would be APIs for implementing OAuth2 protocol, such as validating access tokens and refreshing tokens. This is out of scope of this problem.
[Mid-level deep dive topic. It is almost a mandatory topic.]
For identity management, consistency is critical. For example, if admin deactivates or locks a user's account due to suspicious activities, you really want to make sure that the user cannot log in to the system, immediately. Eventual consistency (at some point in the future, that user would stop being able to log in) would not do.
User Data size is not huge at 10TB. It would also receive more read requests than write requests.
Therefore, we can use RDB for the user account storage.
User table:
AppUser table:
App table:
AppUser table joins User and App table. User and App have many-to-many relationship.
AuthFactor table:
AuthFactor table has a foreign key to User table. User and AuthFactor have one-to-many relationship.
[Senior-level deep dive topic]
The main functionality of authentication can be implemented primarily with read access to the database: find user on User table, read password hash to validate the password user typed, etc. Since we are build a low response-time system with scalability in mind, it is important that this operation is optimized. By keeping accesses to User and AuthFactor tables primarily read-only, we can take advantage of caching and RDB's read replicas for performance and scalability.
But there are write operations in authentication. The system should record who logged in at what time, from which device, using which authentication method, and so on. This information is critical for threat detection and analytics.
These data will be write-heavy and larger than the user related tables. Write pattern will be primarily append-only. Therefore, a LSM based storage would be ideal to store it. The logs will also be timestamped. We can expect time-range queries (e.g., show all the users who logged in to this app between 8AM and 5PM). Therefore, Time-Sequence DB makes sense for Log Database.
Client can be a browser or a rich client (e.g. on a mobile device).
Authentication Service takes all the requests. It uses data in User DB to authenticate users. It writes authentication logs to Log Database. It uses cache (e.g. Redis) to improve response time and scalability.
Client sends requests to API Gateway.
API Gateway forwards the request to one of the Authentication Services. Because this service is stateless, we can use weighted round robin (round robin which prefers to send requests to less loaded servers with more capacity) to distribute requests.
Authentication Services looks up user in User DB using user_id. It then uses data in AuthFactor table to authenticate the user.
[Senior-level deep dive topic]
To support Multi Factor Authentication, we need multiple ways to authenticate a user: password (common but insecure), one time password, Passkey (on WebAuthn protocol), challenge-response (e.g. with smart card), and so on.
Let's look at how it works with password, as it is still the most common authentication method.
One of the important points in this process is that the client does NOT send password to the server for security reasons. If an attacker is sniffing the network, they would be able to steal the hash, instead of the password. Stolen password is extremely dangerous because users tend to reuse the same password in many services. Password hash can be used in only this service, and it can be changed, for example by using a different salt in the hash function.
This process is also designed to handle other authentication methods. The client can send different auth token in Step (4). For example, it may send a signed nonce for Passkey authentication. It may send one time passcode (instead of password hash), and so on. This is how this system can support multi-factor authentication.
See discussion on "Separation of Read and Write Stores" above.
User DB should be partitioned for scalability and response time. user_id is a good choice for partitioning. Users should usually consume similar amount of space and computation on average, because a human user should log in to the system only a few times a day. If some users log in a lot (e.g., thousands or millions of times a day), that would probably indicate a malicious intent or faulty software. Such activities should be blocked or retained via rate limiting.
As such, we do not anticipate large difference in activities between users. However, we should monitor activities regularly. If they show large differences, we should consider distributing load via consistent hashing.
User DB should also be replicated for fault tolerance and scalability. We can have multiple read replicas per primary node. Read replicas help in multiple ways:
All components must be monitored for their health status, resource status, and latency.
Databases (RDB, Time Series DB) and Cache (e.g. Redis) all have built in fault tolerance functionalities such as replication and back up. We should take advantage of that.
Authn Service is stateless, so we can run multiple copies of it and distribute load among them.
There would be user errors, too, for example, abandoning login session, typing wrong passwords too many times, and so on. Appropriate timeouts and user lock-out policy should be implemented.
Privacy and Data Residency laws may impact where data have to reside. For example, GDPR (European privacy law) may require that, if the user resides in Europe, the data about this user must be stored in Europe.
In such case, we cannot solely rely on partitioning (e.g. using hash of user_id). We may consider a cell architecture. A cell is a unit which contains all the nodes (services, DBs, cache, ...) for a particular group of users. For example, in this example, we may create a cell in Europe that stores and handles all the users from Europe.