TL;DR
I don't see anything in your provided code that would need to be protected against timing attacks. The purpose of timing attacks is to learn the plaintext secret, but since your code does not handle the plaintext secret (only a hash of it), your code couldn't leak the plaintext password even if it wanted to.
Technically your hash comparison should be constant time, but see below for why I'm not worried about this in practice.
Background on timing attacks
Let's take a step back and ask what timing attacks are trying to prevent. With passwords, as with all of crypto, you are trying to exploit timing differences in the function that is handling secret data in order to learn the secret.
The classic example is testing for equality between a stored password and a provided password using lazy string compare:
boolean compare(String str1, String str2)
for (i=0; i<len(str1); i++)
if (str1[i] != str2[i]) return false;
return true;
Here you can learn the password one character at a time because when you get the first character right, the string compare will take slightly longer.
What about hashed passwords?
What if you are comparing password hashes rather than plaintext? In theory, if an attacker learns the first character of the password hash, they have gained information, so that is technically an information leak. However, in practice, a timing attack against hashed passwords would require the attacker to submit password candidates p
where they can control hash(p)
one character at a time (ie first a*
, then aa*
...). This is hard. In fact, finding a plaintext whose hash has a given prefix is exactly the cryptographic problem that keeps bitcoin and blockchain secure, and that's on SHA-256; doing it on PBKDF2 or Argon2 would be even harder.
Does your code need to be constant time?
Given the argument above about handling password hashes, it doesn't really matter if this line is constant-time or not:
return verify(password_hash, user.password_hash)
It also does not matter at all whether this line is constant-time; who cares if they can learn password
through a complex side-channel attack, they just submitted it to you, they already know it.
password_hash = slow_hash(password)
IMO it also doesn't matter whether your DB queries are constant time. The attacker would be learning how full your DB is, how much backend network congestion and server load you have, etc, none of which helps them crack passwords. (if your product security model does want to protect info about backend load, that's a much harder and completely separate question).
Bonus: adding random delays
While not directly mentioned in your question, I've written before about how adding random timing delays increases the number of statistical samples (ie the number of login attempts) that an attacker needs in order to perform the statistical attack, but it's ultimately not a solution.