In Denmark, we've had a digital national identity service in operation since 2010, used for example to get access to secure e-mail and government online services.
It was revamped last year, in October 2021, introducing a new login mechanism to improve security.
The new login mechanism (MitID) is a three-step process:
- Input username and continue.
- Open app.
- Review and swipe.
Unlike the original system, in the revamp of 2021, the user is not prompted to open the app.
In principle, this is a considerable improvement since the user is actively participating in the login flow. In the previous system, the user instead required a password in addition to the username. And passwords tend to be weak, for one reason or another.
Notice however, that a login-session can be opened simply by knowing the username.
The service is now facing criticism after it has been discovered that a malicious actor can block a user from using the service if the username follows some template such as <firstname>.<lastname> (or whatever other means of guessing it).
Basically, after N failed attempts to login using a valid username, the account is suspended until manual intervention.
Needless to say, this is not acceptable for the user, nor is it scalable for the operator. But the first point is really the interesting one: a digital identity service should always allow the real user to login!
The trouble is of course not new. If multiple sessions are initiated at roughly the same time, which one is the right one? The most simple control available is to simply deny all attempts, but this includes the real user. Waiting for some amount of time to elapse is not a cure because the malicious actor can simply repeat the process.
The fundamental problem here is that a more or less arbitrary number of malicious login-sessions can be opened at any time for a given username. The internet is not a friendly place and distributed attacks are feasible. Traditional techniques such as blocking IPs are not adequate today.
The real user must somehow pair the real login attempt with the app, disambiguating between the login attempts by the malicious actor.
There are lots of ways to devise such a mechanism. We want one that adapts to the situation. If there's just one login-session, we don't need any mitigation, but if there are multiple sessions, we want an effective means of discarding the bad ones, leaving just the single, real one.
Inspired by Apple ID, we'll use a verification model based on two-digit numbers, but expand to a matrix of 16 choices, ordered by value for convenience.
- 09
- 13
- 17
- 26
- 29
- 32
- 41
- 50
- 58
- 62
- 64
- 77
- 81
- 84
- 95
- 96
The idea is that the user will take a certain action using the app based on the numbers shown on the screen.
For example, pick out a sequence of numbers:
- 84
- 09
- 26
- 58
If we require a specific ordered sequence of 4 elements, we get a total of 43,680 combinations. Adding just two more gives us 5,765,760 combinations.
No matter the number of bad login-sessions attempted at any given time, we can always provide such a sequence to disambiguate, making it exceedingly unlikely that a malicious actor gets through.
The new logic kicks in only when the user opens the app. At this time, the system knows the number of concurrent login-sessions. We'll limit the validity of a login-session to a short amount of time, for example 30 seconds.
- If the number of sessions is 1, we'll revert to the normal behavior.
- Otherwise, determine an appropriate number of items to choose from.
- Each login-session will show a sequence with this many items.
- The user will choose the items from the right sequence, selecting each number in the given order.
This system can be trivially implemented to run at scale using a key/value store.