Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to design a stateless REST Login with 2 Factor Authentication (2FA)?

I'm struggling with the concept of how to design a stateless RESTful authentication API with multi-factor authentication.

Almost by definition, the need of a 2FA requires multiple states; logging in with a username/password, then submitting a "code" (either a TOTP, SMS-code, answer to a verification question, etc). This further implies a finite-state-machine (FSM) of some sort.

As far as I can tell, the only options which exist in order to maintain a stateless mechanism are:

  1. the client must transmit some state information (ex: current FSM state) when submitting data to transition to the next state,
  2. the state must be persisted on the server side,
  3. the client must transmit ALL data at every request which allowed it to reach the current state

Obviously transmitting ALL data is nonsensical. So this would imply either transmitting state information (opaque or otherwise) in the request or maintaining state on the server.

Or is there some other technique that I am missing?

like image 925
Eric B. Avatar asked Jan 23 '17 19:01

Eric B.


People also ask

How do you implement 2FA authentication?

On the desktop, you access it by going to Settings > Security and Login(Opens in a new window). Under Two-Factor Authentication, click Edit on the right. On the next screen, select how you'd like to receive your second form of authentication: a text message, authenticator app, or physical security key.

What are the 3 ways of 2 factor authentication?

Things you know (knowledge), such as a password or PIN. Things you have (possession), such as a badge or smartphone. Things you are (inherence), such as a biometric like fingerprints or voice recognition.


1 Answers

I'm adding the solution I came up with in case it is beneficial for someone else in the future. Please note that in this case, PVQ stands for "Personal Validation Question" (ie: Knowledge-Based-Authentication).

At the end, I designed my login endpoint to require:

  • Authorization header (which is a 2FA token) : Authorization: authType=”PVQ” token=”<tokenid>”
  • username
  • password

If the Authorization header is missing, the endpoint returns a 401 and sets a WWW-Authenticate header, indicating that a 2FA token (ie: Authorization header) is required to login. param could be PVQ, SMS, TOTP, etc (based on the user's configuration)

WWW-Authenticate : authType="PVQ"

If the client receives a 401/WWW-Authenticate response, it is its responsibility to call the 2FA endpoints:

  • challenge/get (receive a challenge token)

    • Client: sends username/password
    • Server: Responds with an ID, and either
      • a question (PVQ),
      • or just sends sends an SMS code via 3rd party SMS provider
  • challenge/verify (receive the 2FA Token needed for the Authorization header)

    • Client: sends
      • ID received in the challenge/get
      • username/password
      • response to the challenge (ie: text answer to a PVQ, or SMS code, or TOTP code)
    • Server: returns
      • 2FA token value

The client can now call the login endpoint with the required: username/password/Authentication token.

In the end, there is not "state" per say that the client returns to the server, but the tradeoff for this, is that the username/password combination must be sent to every request for the 2FA subsystem.

On the server side, there is some state information stored in the DB in the context of the SMS code or PVQ question that was sent to the user, as well as an ephemeral Authentication 2FA token (single use, and fixed TTL).

like image 62
Eric B. Avatar answered Oct 04 '22 13:10

Eric B.