5

I'm building a long-lived session system which requires that users verify their authentication before performing "dangerous" operations, such as changing critical settings, deleting their account, etc. This requirement is analogous to Facebook requiring you re-enter your password before changing your email address, for example. I would like to allow the user to make dangerous changes within some period of time T after verifying their password.

The system will scale horizontally, so remembering "who validated when" in memory isn't possible and I'd like to avoid storing the information in the database, and looking it up on each "dangerous" action. I believe that leaves generating some secure time-sensitive token as my best alternative.

Here's my approach to generating and verifying this token, please let me know if you think I've overlooked anything, or am making some foolish security error. All client-server communication is over TLS/SSL.

1. Alice wants to perform some "dangerous" operation (client side).
2. Alice is presented with a "verify password" modal or similar UI element
   where she enters her password.
3. The client-side system sends the password to the server.
4. The server checks if the password matches Alice's stored password.
5. If the passwords match, a token is generated as follows:
   a. Let `id` be Alice's user ID (UUID), `t` be a fixed length 
      string representation of the current timestamp, and `secret` 
      be a secret key appropriately protected on the server.
   b. Generate the secure part as `k = (string) HMAC(secret, id + t)` 
      (Assume `k` to be typecasted or converted to a string representation.)
   c. Finally, let `token = k + t` where `+` is the string concatenation
      operator 
6. Send the token to Alice.
7. For each "dangerous" operation, Alice includes `token` with the request. 
8. The server can verify the `token` by splitting `k` and `t` and
   validating `k` as in step 5.b above, and verify that time `T` hasn't
   elapsed since Alice verified her password.

I believe this approach solves the problem. Suggestions? Am I missing something?

  • I could be wrong, but if the secret isn't tied to a particular user and session, the loss of a single shared secret key will allow an attacker to arbitrarily generate tokens for all users. – Jedi Jun 17 '16 at 18:55
  • @Jedi Correct, if the secret is lost an attacker could potentially generate arbitrary tokens for any user, assuming they could guess a valid UUID (which would be quite difficult). However, the system I'm describing above isn't meant to initially authenticate a user, only to ensure an already logged-in user with a valid session has recently re-authenticated. @sebastian-nielsen also mentioned including the session ID in the hash, which would also require an attacker to guess a user's session ID. But ultimately, if an attacker can gain access to a secret key, you've got a much bigger problem. – Joshua Toenyes Jun 17 '16 at 19:04
  • Adding the password hash could be a idea too. So you use the password hash from the database to reconstruct the token when verifying. This ensures that a token and session belongning to a compromised & changed password, cannot be used. Of course, this means the user always have to reauthenticate after a password change. – sebastian nielsen Jun 17 '16 at 19:51
  • @sebastiannielsen Another good idea. The same could be ensured by regenerating the session ID whenever the password is changed (and invalidating the previous session ID). Doing that (which is a bit off topic) ensures that the user is immediately logged-out of all other devices/browsers if/when they change their password. – Joshua Toenyes Jun 17 '16 at 20:10
  • The idea is that you include as much information as possible in the hash in question, that you can regenerate when verifying the hash. You could include like uid, sessionid, reduced-precision time, IP number, password hash, etc. More = better, as this makes the resulting hash more unique and harder to guess the content of. – sebastian nielsen Jun 17 '16 at 20:59
4

I would suggest making t a reduced-precision timestamp instead, where you set the precision to be half of the wish precision. The reason for this, is to both avoid having to send more data than neccessary to the client, which increases security, but also, to be able to filter the data on the server side more effecively, to avoid attacks that rise out of unsafe/tainted input.

So for example, if you want the token to alive for 10 minutes, you make a timestamp that will change roughtly once every 5 minutes.

Using a standard unix timestamp, divide this with the half number of seconds you want the token to be alive for, and strip off the decimal.

So 1466182513, which is current time. Divide it with 300 (5 minutes) and int() it. You get: 4887275

which will be valid from:

Fri, 17 Jun 2016 16:55:00 GMT

to

Fri, 17 Jun 2016 16:59:59 GMT

This decimal value can be included in the hash. So the value 4887275 is effectively a "clock" that "ticks" each 5'th minute.

Now to the trick: When you verify the hash, you "try" with both the reduced-precision value for the CURRENT time, but also the reduced-precision value immediately before this. This effectively means that the client will be able to do "sensitive actions" anywhere between 5 to 10 minutes from the time when they input the password.

Don't misunderstand this now. What I mean, is that the token will expire anywhere between 5 to 10 minutes from issuance, and this expiry will depend on when, in the timeframe, they input the password.

Eg, in the above example, you try to validate the hash both with the value 4887275, which is from current time, and value 4887274, which is the value for about 1-5 minutes ago.

Of course, you can divide with other values to get other precision, for example 600 to gain a validity between 10 to 20 minutes.

The reason you should try also the timestamp immediately before, is that, lets say a person enter his password at Fri, 17 Jun 2016 16:54:59. Now he has 1 second to carry out his sensitive action, unless we also try the value immediately before. Thats is why you should select a value half of the wish precision.

This means you don't need to send the timestamp to the client. You just send the hash, as you can easily reconstruct ALL of data in the hash.

A good idea is also to include the session ID in the hash, so the token is effectively invalidated as soon as the user logs out or the session is destroyed for some other reason. Also the password hash is a good idea to include, so if the user changes his password, any tokens become invalid.

  • Thank you for the suggestion! That definitely the process and generated token more elegant. From a security standpoint, is there an advantage to not sending the concatenated timestamp? – Joshua Toenyes Jun 17 '16 at 17:15
  • @JoshuaToenyes I edited the answer to make it more simple, avoiding the process of going through binary. Yes, the less security-sensitive data you send, more secure it gets. Also, by limiting data to strictly the hash, you can also filter data more effectively, so you avoid any attacks rising out from unsafe input. – sebastian nielsen Jun 17 '16 at 17:35
  • @JoshuaToenyes I would also recommend including the session ID in the hash, so logging out or destroying the session will also invalidate any "sensitive action" token. – sebastian nielsen Jun 17 '16 at 17:38
  • 1
    I agree with @sebastiannielsen's answer and comments. You may also wish to make this reduced-precision window slide, so that if a user uses a token belonging to the previous time window, he/she is automatically assigned an update. Thus you could have two session timeouts-- idle and active. – Jedi Jun 17 '16 at 18:52
0

Kerberos is an effective protocol that can be used to do this. Details can be found here: https://en.wikipedia.org/wiki/Kerberos_(protocol) . It does require a little setup though.

It is a time-sensitive authentication and authorization method. Basically, you get a Ticket-Granting-Ticket from the authentication server, and that gets presented to the Ticket-Granting-Service to obtain a ticket that can be used to get authorized to a specific service. When that service is presented with the ticket, it authorizes the user to perform whatever service the server provides per the ticket. If the ticket is expired, you have to get a new ticket from the TGS.

The TGS can be required to verify the user's password before providing the ticket that authorizes the user to make said changes. When that ticket expires, they user will need to reauthenticate to get a new ticket.

  • Kerberos is competely and utter overkill for the application the OP is making. Its like eliminating a mosquito with a nuclear bomb. Kerberos is more suitable in applications where theres multiple "relying parties" which neccessarly does not need to trust each other, for example single sign on in corporate enviroments. All this ticket mess is completely overkill for just a simple application where user is "sudo"ing, like Github Sudo Mode. – sebastian nielsen Jun 17 '16 at 17:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.